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 <rtl/ustrbuf.hxx>
31 #include <sal/log.hxx>
33 #include "nodelist.hxx"
34 #include "xpathobject.hxx"
37 #include "../dom/document.hxx"
39 #include <comphelper/servicehelper.hxx>
40 #include <cppuhelper/supportsservice.hxx>
42 using namespace css::io
;
43 using namespace css::uno
;
44 using namespace css::xml::dom
;
45 using namespace css::xml::xpath
;
46 using css::lang::XMultiServiceFactory
;
51 Reference
< XInterface
> CXPathAPI::_getInstance(const Reference
< XMultiServiceFactory
>& rSMgr
)
53 return static_cast<XXPathAPI
*>(new CXPathAPI(rSMgr
));
57 CXPathAPI::CXPathAPI(const Reference
< XMultiServiceFactory
>& rSMgr
)
62 OUString
CXPathAPI::_getImplementationName()
64 return "com.sun.star.comp.xml.xpath.XPathAPI";
67 Sequence
<OUString
> CXPathAPI::_getSupportedServiceNames()
69 return { "com.sun.star.xml.xpath.XPathAPI" };
72 Sequence
< OUString
> SAL_CALL
CXPathAPI::getSupportedServiceNames()
74 return CXPathAPI::_getSupportedServiceNames();
77 OUString SAL_CALL
CXPathAPI::getImplementationName()
79 return CXPathAPI::_getImplementationName();
82 sal_Bool SAL_CALL
CXPathAPI::supportsService(const OUString
& aServiceName
)
84 return cppu::supportsService(this, aServiceName
);
87 void SAL_CALL
CXPathAPI::registerNS(
88 const OUString
& aPrefix
,
91 ::osl::MutexGuard
const g(m_Mutex
);
93 m_nsmap
.emplace(aPrefix
, aURI
);
96 void SAL_CALL
CXPathAPI::unregisterNS(
97 const OUString
& aPrefix
,
100 ::osl::MutexGuard
const g(m_Mutex
);
102 if ((m_nsmap
.find(aPrefix
))->second
== aURI
) {
103 m_nsmap
.erase(aPrefix
);
107 // register all namespaces stored in the namespace list for this object
108 // with the current xpath evaluation context
109 static void lcl_registerNamespaces(
110 xmlXPathContextPtr ctx
,
111 const nsmap_t
& nsmap
)
113 OString oprefix
, ouri
;
114 for (const auto& rEntry
: nsmap
)
116 oprefix
= OUStringToOString(rEntry
.first
, RTL_TEXTENCODING_UTF8
);
117 ouri
= OUStringToOString(rEntry
.second
, RTL_TEXTENCODING_UTF8
);
118 xmlChar
const *p
= reinterpret_cast<xmlChar
const *>(oprefix
.getStr());
119 xmlChar
const *u
= reinterpret_cast<xmlChar
const *>(ouri
.getStr());
120 (void)xmlXPathRegisterNs(ctx
, p
, u
);
124 // get all ns decls on a node (and parent nodes, if any)
125 static void lcl_collectNamespaces(
126 nsmap_t
& rNamespaces
, Reference
< XNode
> const& xNamespaceNode
)
128 DOM::CNode
*const pCNode(comphelper::getUnoTunnelImplementation
<DOM::CNode
>(xNamespaceNode
));
129 if (!pCNode
) { throw RuntimeException(); }
131 ::osl::MutexGuard
const g(pCNode
->GetOwnerDocument().GetMutex());
133 xmlNodePtr pNode
= pCNode
->GetNodePtr();
134 while (pNode
!= nullptr) {
135 xmlNsPtr curDef
= pNode
->nsDef
;
136 while (curDef
!= nullptr) {
137 const xmlChar
* pHref
= curDef
->href
;
138 OUString
aURI(reinterpret_cast<char const *>(pHref
), strlen(reinterpret_cast<char const *>(pHref
)), RTL_TEXTENCODING_UTF8
);
139 const xmlChar
* pPre
= curDef
->prefix
;
140 OUString
aPrefix(reinterpret_cast<char const *>(pPre
), strlen(reinterpret_cast<char const *>(pPre
)), RTL_TEXTENCODING_UTF8
);
141 // we could already have this prefix from a child node
142 rNamespaces
.emplace(aPrefix
, aURI
);
143 curDef
= curDef
->next
;
145 pNode
= pNode
->parent
;
149 static void lcl_collectRegisterNamespaces(
150 CXPathAPI
& rAPI
, Reference
< XNode
> const& xNamespaceNode
)
153 lcl_collectNamespaces(namespaces
, xNamespaceNode
);
154 for (const auto& rEntry
: namespaces
)
156 rAPI
.registerNS(rEntry
.first
, rEntry
.second
);
160 // register function and variable lookup functions with the current
161 // xpath evaluation context
162 static void lcl_registerExtensions(
163 xmlXPathContextPtr ctx
,
164 const extensions_t
& extensions
)
166 for (const auto& rExtensionRef
: extensions
)
168 Libxml2ExtensionHandle aHandle
= rExtensionRef
->getLibxml2ExtensionHandle();
169 if ( aHandle
.functionLookupFunction
!= 0 )
171 xmlXPathRegisterFuncLookup(ctx
,
172 reinterpret_cast<xmlXPathFuncLookupFunc
>(
173 sal::static_int_cast
<sal_IntPtr
>(aHandle
.functionLookupFunction
)),
174 reinterpret_cast<void*>(
175 sal::static_int_cast
<sal_IntPtr
>(aHandle
.functionData
)));
177 if ( aHandle
.variableLookupFunction
!= 0 )
179 xmlXPathRegisterVariableLookup(ctx
,
180 reinterpret_cast<xmlXPathVariableLookupFunc
>(
181 sal::static_int_cast
<sal_IntPtr
>(aHandle
.variableLookupFunction
)),
182 reinterpret_cast<void*>(
183 sal::static_int_cast
<sal_IntPtr
>(aHandle
.variableData
)));
189 * Use an XPath string to select a nodelist.
191 Reference
< XNodeList
> SAL_CALL
CXPathAPI::selectNodeList(
192 const Reference
< XNode
>& contextNode
,
193 const OUString
& expr
)
195 Reference
< XXPathObject
> xobj
= eval(contextNode
, expr
);
196 return xobj
->getNodeList();
200 * same as selectNodeList but registers all name space declarations found on namespaceNode
202 Reference
< XNodeList
> SAL_CALL
CXPathAPI::selectNodeListNS(
203 const Reference
< XNode
>& contextNode
,
204 const OUString
& expr
,
205 const Reference
< XNode
>& namespaceNode
)
207 lcl_collectRegisterNamespaces(*this, namespaceNode
);
208 return selectNodeList(contextNode
, expr
);
212 * Same as selectNodeList but returns the first node (if any)
214 Reference
< XNode
> SAL_CALL
CXPathAPI::selectSingleNode(
215 const Reference
< XNode
>& contextNode
,
216 const OUString
& expr
)
218 Reference
< XNodeList
> aList
= selectNodeList(contextNode
, expr
);
219 Reference
< XNode
> aNode
= aList
->item(0);
224 * Same as selectSingleNode but registers all namespaces declared on
227 Reference
< XNode
> SAL_CALL
CXPathAPI::selectSingleNodeNS(
228 const Reference
< XNode
>& contextNode
,
229 const OUString
& expr
,
230 const Reference
< XNode
>& namespaceNode
)
232 lcl_collectRegisterNamespaces(*this, namespaceNode
);
233 return selectSingleNode(contextNode
, expr
);
236 static OUString
make_error_message(xmlErrorPtr pError
)
240 if (pError
->message
) {
241 buf
.appendAscii(pError
->message
);
243 int line
= pError
->line
;
245 buf
.append("Line: ");
246 buf
.append(static_cast<sal_Int32
>(line
));
249 int column
= pError
->int2
;
251 buf
.append("Column: ");
252 buf
.append(static_cast<sal_Int32
>(column
));
256 buf
.append("no error argument!");
258 OUString msg
= buf
.makeStringAndClear();
265 __attribute__ ((format (printf
, 2, 3)))
267 static void generic_error_func(void *, const char *format
, ...)
272 va_start(args
, format
);
274 #define vsnprintf _vsnprintf
276 vsnprintf(str
, sizeof(str
), format
, args
);
279 SAL_WARN("unoxml", "libxml2 error: " << str
);
282 static void structured_error_func(void *, xmlErrorPtr error
)
284 SAL_WARN("unoxml", "libxml2 error: " << make_error_message(error
));
290 * evaluates an XPath string. relative XPath expressions are evaluated relative to
293 Reference
< XXPathObject
> SAL_CALL
CXPathAPI::eval(
294 Reference
< XNode
> const& xContextNode
,
295 const OUString
& expr
)
297 if (!xContextNode
.is()) { throw RuntimeException(); }
300 extensions_t extensions
;
303 ::osl::MutexGuard
const g(m_Mutex
);
305 extensions
= m_extensions
;
308 // get the node and document
309 ::rtl::Reference
<DOM::CDocument
> const pCDoc(
310 dynamic_cast<DOM::CDocument
*>( comphelper::getUnoTunnelImplementation
<DOM::CNode
>(
311 xContextNode
->getOwnerDocument())));
312 if (!pCDoc
.is()) { throw RuntimeException(); }
314 DOM::CNode
*const pCNode
= comphelper::getUnoTunnelImplementation
<DOM::CNode
>(xContextNode
);
315 if (!pCNode
) { throw RuntimeException(); }
317 ::osl::MutexGuard
const g(pCDoc
->GetMutex()); // lock the document!
319 xmlNodePtr
const pNode
= pCNode
->GetNodePtr();
320 if (!pNode
) { throw RuntimeException(); }
321 xmlDocPtr pDoc
= pNode
->doc
;
323 /* NB: workaround for #i87252#:
324 libxml < 2.6.17 considers it an error if the context
325 node is the empty document (i.e. its xpathCtx->doc has no
326 children). libxml 2.6.17 does not consider it an error.
327 Unfortunately, old libxml prints an error message to stderr,
328 which (afaik) cannot be turned off in this case, so we handle it.
330 if (!pDoc
->children
) {
331 throw XPathException();
334 /* Create xpath evaluation context */
335 std::shared_ptr
<xmlXPathContext
> const xpathCtx(
336 xmlXPathNewContext(pDoc
), xmlXPathFreeContext
);
337 if (xpathCtx
== nullptr) { throw XPathException(); }
340 xpathCtx
->node
= pNode
;
342 xpathCtx
->error
= structured_error_func
;
343 xmlSetGenericErrorFunc(nullptr, generic_error_func
);
345 // register namespaces and extension
346 lcl_registerNamespaces(xpathCtx
.get(), nsmap
);
347 lcl_registerExtensions(xpathCtx
.get(), extensions
);
350 OString o1
= OUStringToOString(expr
, RTL_TEXTENCODING_UTF8
);
351 xmlChar
const *pStr
= reinterpret_cast<xmlChar
const *>(o1
.getStr());
352 std::shared_ptr
<xmlXPathObject
> const xpathObj(
353 xmlXPathEval(pStr
, xpathCtx
.get()), xmlXPathFreeObject
);
354 xmlSetGenericErrorFunc(nullptr, nullptr);
355 if (nullptr == xpathObj
) {
356 // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
357 throw XPathException();
359 Reference
<XXPathObject
> const xObj(
360 new CXPathObject(pCDoc
, pCDoc
->GetMutex(), xpathObj
));
365 * same as eval but registers all namespace declarations found on namespaceNode
367 Reference
< XXPathObject
> SAL_CALL
CXPathAPI::evalNS(
368 const Reference
< XNode
>& contextNode
,
369 const OUString
& expr
,
370 const Reference
< XNode
>& namespaceNode
)
372 lcl_collectRegisterNamespaces(*this, namespaceNode
);
373 return eval(contextNode
, expr
);
377 * uses the service manager to create an instance of the service denoted by aName.
378 * If the returned object implements the XXPathExtension interface, it is added to the list
379 * of extensions that are used when evaluating XPath strings with this XPathAPI instance
381 void SAL_CALL
CXPathAPI::registerExtension(
382 const OUString
& aName
)
384 ::osl::MutexGuard
const g(m_Mutex
);
386 // get extension from service manager
387 Reference
< XXPathExtension
> const xExtension(
388 m_aFactory
->createInstance(aName
), UNO_QUERY_THROW
);
389 m_extensions
.push_back(xExtension
);
393 * registers the given extension instance to be used by XPath evaluations performed through this
396 void SAL_CALL
CXPathAPI::registerExtensionInstance(
397 Reference
< XXPathExtension
> const& xExtension
)
399 if (!xExtension
.is()) {
400 throw RuntimeException();
402 ::osl::MutexGuard
const g(m_Mutex
);
403 m_extensions
.push_back( xExtension
);
407 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */