1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "xpathapi.hxx"
25 #include <libxml/tree.h>
26 #include <libxml/xmlerror.h>
27 #include <libxml/xpath.h>
28 #include <libxml/xpathInternals.h>
30 #include <com/sun/star/xml/xpath/XPathException.hpp>
32 #include <rtl/ustrbuf.hxx>
33 #include <sal/log.hxx>
35 #include "xpathobject.hxx"
38 #include "../dom/document.hxx"
40 #include <comphelper/servicehelper.hxx>
41 #include <cppuhelper/supportsservice.hxx>
43 using namespace css::io
;
44 using namespace css::uno
;
45 using namespace css::xml::dom
;
46 using namespace css::xml::xpath
;
51 CXPathAPI::CXPathAPI(const Reference
< XComponentContext
>& rxContext
)
52 : m_xContext(rxContext
)
56 Sequence
< OUString
> SAL_CALL
CXPathAPI::getSupportedServiceNames()
58 return { "com.sun.star.xml.xpath.XPathAPI" };
61 OUString SAL_CALL
CXPathAPI::getImplementationName()
63 return "com.sun.star.comp.xml.xpath.XPathAPI";
66 sal_Bool SAL_CALL
CXPathAPI::supportsService(const OUString
& aServiceName
)
68 return cppu::supportsService(this, aServiceName
);
71 void SAL_CALL
CXPathAPI::registerNS(
72 const OUString
& aPrefix
,
75 std::scoped_lock
const g(m_Mutex
);
77 m_nsmap
.emplace(aPrefix
, aURI
);
80 void SAL_CALL
CXPathAPI::unregisterNS(
81 const OUString
& aPrefix
,
84 std::scoped_lock
const g(m_Mutex
);
86 if ((m_nsmap
.find(aPrefix
))->second
== aURI
) {
87 m_nsmap
.erase(aPrefix
);
91 // register all namespaces stored in the namespace list for this object
92 // with the current xpath evaluation context
93 static void lcl_registerNamespaces(
94 xmlXPathContextPtr ctx
,
97 OString oprefix
, ouri
;
98 for (const auto& rEntry
: nsmap
)
100 oprefix
= OUStringToOString(rEntry
.first
, RTL_TEXTENCODING_UTF8
);
101 ouri
= OUStringToOString(rEntry
.second
, RTL_TEXTENCODING_UTF8
);
102 xmlChar
const *p
= reinterpret_cast<xmlChar
const *>(oprefix
.getStr());
103 xmlChar
const *u
= reinterpret_cast<xmlChar
const *>(ouri
.getStr());
104 (void)xmlXPathRegisterNs(ctx
, p
, u
);
108 // get all ns decls on a node (and parent nodes, if any)
109 static void lcl_collectNamespaces(
110 nsmap_t
& rNamespaces
, Reference
< XNode
> const& xNamespaceNode
)
112 DOM::CNode
*const pCNode(dynamic_cast<DOM::CNode
*>(xNamespaceNode
.get()));
113 if (!pCNode
) { throw RuntimeException(); }
115 ::osl::MutexGuard
const g(pCNode
->GetOwnerDocument().GetMutex());
117 xmlNodePtr pNode
= pCNode
->GetNodePtr();
118 while (pNode
!= nullptr) {
119 xmlNsPtr curDef
= pNode
->nsDef
;
120 while (curDef
!= nullptr) {
121 const xmlChar
* pHref
= curDef
->href
;
122 OUString
aURI(reinterpret_cast<char const *>(pHref
), strlen(reinterpret_cast<char const *>(pHref
)), RTL_TEXTENCODING_UTF8
);
123 const xmlChar
* pPre
= curDef
->prefix
;
124 OUString
aPrefix(reinterpret_cast<char const *>(pPre
), strlen(reinterpret_cast<char const *>(pPre
)), RTL_TEXTENCODING_UTF8
);
125 // we could already have this prefix from a child node
126 rNamespaces
.emplace(aPrefix
, aURI
);
127 curDef
= curDef
->next
;
129 pNode
= pNode
->parent
;
133 static void lcl_collectRegisterNamespaces(
134 CXPathAPI
& rAPI
, Reference
< XNode
> const& xNamespaceNode
)
137 lcl_collectNamespaces(namespaces
, xNamespaceNode
);
138 for (const auto& rEntry
: namespaces
)
140 rAPI
.registerNS(rEntry
.first
, rEntry
.second
);
144 // register function and variable lookup functions with the current
145 // xpath evaluation context
146 static void lcl_registerExtensions(
147 xmlXPathContextPtr ctx
,
148 const extensions_t
& extensions
)
150 for (const auto& rExtensionRef
: extensions
)
152 Libxml2ExtensionHandle aHandle
= rExtensionRef
->getLibxml2ExtensionHandle();
153 if ( aHandle
.functionLookupFunction
!= 0 )
155 xmlXPathRegisterFuncLookup(ctx
,
156 reinterpret_cast<xmlXPathFuncLookupFunc
>(
157 sal::static_int_cast
<sal_IntPtr
>(aHandle
.functionLookupFunction
)),
158 reinterpret_cast<void*>(
159 sal::static_int_cast
<sal_IntPtr
>(aHandle
.functionData
)));
161 if ( aHandle
.variableLookupFunction
!= 0 )
163 xmlXPathRegisterVariableLookup(ctx
,
164 reinterpret_cast<xmlXPathVariableLookupFunc
>(
165 sal::static_int_cast
<sal_IntPtr
>(aHandle
.variableLookupFunction
)),
166 reinterpret_cast<void*>(
167 sal::static_int_cast
<sal_IntPtr
>(aHandle
.variableData
)));
173 * Use an XPath string to select a nodelist.
175 Reference
< XNodeList
> SAL_CALL
CXPathAPI::selectNodeList(
176 const Reference
< XNode
>& contextNode
,
177 const OUString
& expr
)
179 Reference
< XXPathObject
> xobj
= eval(contextNode
, expr
);
180 return xobj
->getNodeList();
184 * same as selectNodeList but registers all name space declarations found on namespaceNode
186 Reference
< XNodeList
> SAL_CALL
CXPathAPI::selectNodeListNS(
187 const Reference
< XNode
>& contextNode
,
188 const OUString
& expr
,
189 const Reference
< XNode
>& namespaceNode
)
191 lcl_collectRegisterNamespaces(*this, namespaceNode
);
192 return selectNodeList(contextNode
, expr
);
196 * Same as selectNodeList but returns the first node (if any)
198 Reference
< XNode
> SAL_CALL
CXPathAPI::selectSingleNode(
199 const Reference
< XNode
>& contextNode
,
200 const OUString
& expr
)
202 Reference
< XNodeList
> aList
= selectNodeList(contextNode
, expr
);
203 Reference
< XNode
> aNode
= aList
->item(0);
208 * Same as selectSingleNode but registers all namespaces declared on
211 Reference
< XNode
> SAL_CALL
CXPathAPI::selectSingleNodeNS(
212 const Reference
< XNode
>& contextNode
,
213 const OUString
& expr
,
214 const Reference
< XNode
>& namespaceNode
)
216 lcl_collectRegisterNamespaces(*this, namespaceNode
);
217 return selectSingleNode(contextNode
, expr
);
220 static OUString
make_error_message(xmlErrorPtr pError
)
224 if (pError
->message
) {
225 buf
.appendAscii(pError
->message
);
227 int line
= pError
->line
;
229 buf
.append("Line: " + OUString::number(static_cast<sal_Int32
>(line
)) + "\n");
231 int column
= pError
->int2
;
233 buf
.append("Column: " + OUString::number(static_cast<sal_Int32
>(column
)) + "\n");
236 buf
.append("no error argument!");
238 OUString msg
= buf
.makeStringAndClear();
245 __attribute__ ((format (printf
, 2, 3)))
247 static void generic_error_func(void *, const char *format
, ...)
252 va_start(args
, format
);
254 #define vsnprintf _vsnprintf
256 vsnprintf(str
, sizeof(str
), format
, args
);
259 SAL_WARN("unoxml", "libxml2 error: " << str
);
262 static void structured_error_func(void *, xmlErrorPtr error
)
264 SAL_WARN("unoxml", "libxml2 error: " << make_error_message(error
));
270 * evaluates an XPath string. relative XPath expressions are evaluated relative to
273 Reference
< XXPathObject
> SAL_CALL
CXPathAPI::eval(
274 Reference
< XNode
> const& xContextNode
,
275 const OUString
& expr
)
277 if (!xContextNode
.is()) { throw RuntimeException(); }
280 extensions_t extensions
;
283 std::scoped_lock
const g(m_Mutex
);
285 extensions
= m_extensions
;
288 // get the node and document
289 ::rtl::Reference
<DOM::CDocument
> const pCDoc(
290 dynamic_cast<DOM::CDocument
*>(xContextNode
->getOwnerDocument().get()));
291 if (!pCDoc
.is()) { throw RuntimeException(); }
293 DOM::CNode
*const pCNode
= dynamic_cast<DOM::CNode
*>(xContextNode
.get());
294 if (!pCNode
) { throw RuntimeException(); }
296 ::osl::MutexGuard
const g(pCDoc
->GetMutex()); // lock the document!
298 xmlNodePtr
const pNode
= pCNode
->GetNodePtr();
299 if (!pNode
) { throw RuntimeException(); }
300 xmlDocPtr pDoc
= pNode
->doc
;
302 /* NB: workaround for #i87252#:
303 libxml < 2.6.17 considers it an error if the context
304 node is the empty document (i.e. its xpathCtx->doc has no
305 children). libxml 2.6.17 does not consider it an error.
306 Unfortunately, old libxml prints an error message to stderr,
307 which (afaik) cannot be turned off in this case, so we handle it.
309 if (!pDoc
->children
) {
310 throw XPathException();
313 /* Create xpath evaluation context */
314 std::shared_ptr
<xmlXPathContext
> const xpathCtx(
315 xmlXPathNewContext(pDoc
), xmlXPathFreeContext
);
316 if (xpathCtx
== nullptr) { throw XPathException(); }
319 xpathCtx
->node
= pNode
;
321 xpathCtx
->error
= structured_error_func
;
322 xmlSetGenericErrorFunc(nullptr, generic_error_func
);
324 // register namespaces and extension
325 lcl_registerNamespaces(xpathCtx
.get(), nsmap
);
326 lcl_registerExtensions(xpathCtx
.get(), extensions
);
329 OString o1
= OUStringToOString(expr
, RTL_TEXTENCODING_UTF8
);
330 xmlChar
const *pStr
= reinterpret_cast<xmlChar
const *>(o1
.getStr());
331 std::shared_ptr
<xmlXPathObject
> const xpathObj(
332 xmlXPathEval(pStr
, xpathCtx
.get()), xmlXPathFreeObject
);
333 xmlSetGenericErrorFunc(nullptr, nullptr);
334 if (nullptr == xpathObj
) {
335 // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
336 throw XPathException();
338 Reference
<XXPathObject
> const xObj(
339 new CXPathObject(pCDoc
, pCDoc
->GetMutex(), xpathObj
));
344 * same as eval but registers all namespace declarations found on namespaceNode
346 Reference
< XXPathObject
> SAL_CALL
CXPathAPI::evalNS(
347 const Reference
< XNode
>& contextNode
,
348 const OUString
& expr
,
349 const Reference
< XNode
>& namespaceNode
)
351 lcl_collectRegisterNamespaces(*this, namespaceNode
);
352 return eval(contextNode
, expr
);
356 * uses the service manager to create an instance of the service denoted by aName.
357 * If the returned object implements the XXPathExtension interface, it is added to the list
358 * of extensions that are used when evaluating XPath strings with this XPathAPI instance
360 void SAL_CALL
CXPathAPI::registerExtension(
361 const OUString
& aName
)
363 std::scoped_lock
const g(m_Mutex
);
365 // get extension from service manager
366 Reference
< XXPathExtension
> const xExtension(
367 m_xContext
->getServiceManager()->createInstanceWithContext(aName
, m_xContext
), UNO_QUERY_THROW
);
368 m_extensions
.push_back(xExtension
);
372 * registers the given extension instance to be used by XPath evaluations performed through this
375 void SAL_CALL
CXPathAPI::registerExtensionInstance(
376 Reference
< XXPathExtension
> const& xExtension
)
378 if (!xExtension
.is()) {
379 throw RuntimeException();
381 std::scoped_lock
const g(m_Mutex
);
382 m_extensions
.push_back( xExtension
);
386 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
387 unoxml_CXPathAPI_get_implementation(
388 css::uno::XComponentContext
* context
, css::uno::Sequence
<css::uno::Any
> const&)
390 return cppu::acquire(new XPath::CXPathAPI(context
));
393 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */