/src/libreoffice/unoxml/source/xpath/xpathapi.cxx
Line | Count | Source |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
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 . |
18 | | */ |
19 | | |
20 | | #include "xpathapi.hxx" |
21 | | |
22 | | #include <stdarg.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include <libxml/tree.h> |
26 | | #include <libxml/xmlerror.h> |
27 | | #include <libxml/xpath.h> |
28 | | #include <libxml/xpathInternals.h> |
29 | | #include <libxml/xmlIO.h> |
30 | | |
31 | | #include <com/sun/star/xml/xpath/XPathException.hpp> |
32 | | |
33 | | #include <rtl/ustrbuf.hxx> |
34 | | #include <sal/log.hxx> |
35 | | |
36 | | #include "xpathobject.hxx" |
37 | | |
38 | | #include <node.hxx> |
39 | | #include "../dom/document.hxx" |
40 | | |
41 | | #include <comphelper/servicehelper.hxx> |
42 | | #include <cppuhelper/supportsservice.hxx> |
43 | | |
44 | | using namespace css::uno; |
45 | | using namespace css::xml::dom; |
46 | | using namespace css::xml::xpath; |
47 | | |
48 | | namespace XPath |
49 | | { |
50 | | // ctor |
51 | | CXPathAPI::CXPathAPI(const Reference< XComponentContext >& rxContext) |
52 | 515 | : m_xContext(rxContext) |
53 | 515 | { |
54 | 515 | } |
55 | | |
56 | | Sequence< OUString > SAL_CALL CXPathAPI::getSupportedServiceNames() |
57 | 0 | { |
58 | 0 | return { u"com.sun.star.xml.xpath.XPathAPI"_ustr }; |
59 | 0 | } |
60 | | |
61 | | OUString SAL_CALL CXPathAPI::getImplementationName() |
62 | 0 | { |
63 | 0 | return u"com.sun.star.comp.xml.xpath.XPathAPI"_ustr; |
64 | 0 | } |
65 | | |
66 | | sal_Bool SAL_CALL CXPathAPI::supportsService(const OUString& aServiceName) |
67 | 0 | { |
68 | 0 | return cppu::supportsService(this, aServiceName); |
69 | 0 | } |
70 | | |
71 | | void SAL_CALL CXPathAPI::registerNS( |
72 | | const OUString& aPrefix, |
73 | | const OUString& aURI) |
74 | 863 | { |
75 | 863 | std::scoped_lock const g(m_Mutex); |
76 | | |
77 | 863 | m_nsmap.emplace(aPrefix, aURI); |
78 | 863 | } |
79 | | |
80 | | void SAL_CALL CXPathAPI::unregisterNS( |
81 | | const OUString& aPrefix, |
82 | | const OUString& aURI) |
83 | 0 | { |
84 | 0 | std::scoped_lock const g(m_Mutex); |
85 | |
|
86 | 0 | if ((m_nsmap.find(aPrefix))->second == aURI) { |
87 | 0 | m_nsmap.erase(aPrefix); |
88 | 0 | } |
89 | 0 | } |
90 | | |
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, |
95 | | const nsmap_t& nsmap) |
96 | 721 | { |
97 | 721 | OString oprefix, ouri; |
98 | 721 | for (const auto& rEntry : nsmap) |
99 | 1.20k | { |
100 | 1.20k | oprefix = OUStringToOString(rEntry.first, RTL_TEXTENCODING_UTF8); |
101 | 1.20k | ouri = OUStringToOString(rEntry.second, RTL_TEXTENCODING_UTF8); |
102 | 1.20k | xmlChar const *p = reinterpret_cast<xmlChar const *>(oprefix.getStr()); |
103 | 1.20k | xmlChar const *u = reinterpret_cast<xmlChar const *>(ouri.getStr()); |
104 | 1.20k | (void)xmlXPathRegisterNs(ctx, p, u); |
105 | 1.20k | } |
106 | 721 | } |
107 | | |
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) |
111 | 0 | { |
112 | 0 | DOM::CNode *const pCNode(dynamic_cast<DOM::CNode*>(xNamespaceNode.get())); |
113 | 0 | if (!pCNode) { throw RuntimeException(u"Could not use the namespace node in order to collect namespace declarations."_ustr); } |
114 | | |
115 | 0 | ::osl::MutexGuard const g(pCNode->GetOwnerDocument().GetMutex()); |
116 | |
|
117 | 0 | xmlNodePtr pNode = pCNode->GetNodePtr(); |
118 | 0 | while (pNode != nullptr) { |
119 | 0 | xmlNsPtr curDef = pNode->nsDef; |
120 | 0 | while (curDef != nullptr) { |
121 | 0 | const xmlChar* pHref = curDef->href; |
122 | 0 | OUString aURI(reinterpret_cast<char const *>(pHref), strlen(reinterpret_cast<char const *>(pHref)), RTL_TEXTENCODING_UTF8); |
123 | 0 | const xmlChar* pPre = curDef->prefix; |
124 | 0 | 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 | 0 | rNamespaces.emplace(aPrefix, aURI); |
127 | 0 | curDef = curDef->next; |
128 | 0 | } |
129 | 0 | pNode = pNode->parent; |
130 | 0 | } |
131 | 0 | } |
132 | | |
133 | | static void lcl_collectRegisterNamespaces( |
134 | | CXPathAPI & rAPI, Reference< XNode > const& xNamespaceNode) |
135 | 0 | { |
136 | 0 | nsmap_t namespaces; |
137 | 0 | lcl_collectNamespaces(namespaces, xNamespaceNode); |
138 | 0 | for (const auto& rEntry : namespaces) |
139 | 0 | { |
140 | 0 | rAPI.registerNS(rEntry.first, rEntry.second); |
141 | 0 | } |
142 | 0 | } |
143 | | |
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) |
149 | 721 | { |
150 | 721 | for (const auto& rExtensionRef : extensions) |
151 | 0 | { |
152 | 0 | Libxml2ExtensionHandle aHandle = rExtensionRef->getLibxml2ExtensionHandle(); |
153 | 0 | if ( aHandle.functionLookupFunction != 0 ) |
154 | 0 | { |
155 | 0 | xmlXPathRegisterFuncLookup(ctx, |
156 | 0 | reinterpret_cast<xmlXPathFuncLookupFunc>( |
157 | 0 | sal::static_int_cast<sal_IntPtr>(aHandle.functionLookupFunction)), |
158 | 0 | reinterpret_cast<void*>( |
159 | 0 | sal::static_int_cast<sal_IntPtr>(aHandle.functionData))); |
160 | 0 | } |
161 | 0 | if ( aHandle.variableLookupFunction != 0 ) |
162 | 0 | { |
163 | 0 | xmlXPathRegisterVariableLookup(ctx, |
164 | 0 | reinterpret_cast<xmlXPathVariableLookupFunc>( |
165 | 0 | sal::static_int_cast<sal_IntPtr>(aHandle.variableLookupFunction)), |
166 | 0 | reinterpret_cast<void*>( |
167 | 0 | sal::static_int_cast<sal_IntPtr>(aHandle.variableData))); |
168 | 0 | } |
169 | 0 | } |
170 | 721 | } |
171 | | |
172 | | /** |
173 | | * Use an XPath string to select a nodelist. |
174 | | */ |
175 | | Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeList( |
176 | | const Reference< XNode >& contextNode, |
177 | | const OUString& expr) |
178 | 0 | { |
179 | 0 | Reference< XXPathObject > xobj = eval(contextNode, expr); |
180 | 0 | return xobj->getNodeList(); |
181 | 0 | } |
182 | | |
183 | | /** |
184 | | * same as selectNodeList but registers all name space declarations found on namespaceNode |
185 | | */ |
186 | | Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeListNS( |
187 | | const Reference< XNode >& contextNode, |
188 | | const OUString& expr, |
189 | | const Reference< XNode >& namespaceNode) |
190 | 0 | { |
191 | 0 | lcl_collectRegisterNamespaces(*this, namespaceNode); |
192 | 0 | return selectNodeList(contextNode, expr); |
193 | 0 | } |
194 | | |
195 | | /** |
196 | | * Same as selectNodeList but returns the first node (if any) |
197 | | */ |
198 | | Reference< XNode > SAL_CALL CXPathAPI::selectSingleNode( |
199 | | const Reference< XNode >& contextNode, |
200 | | const OUString& expr) |
201 | 0 | { |
202 | 0 | Reference< XNodeList > aList = selectNodeList(contextNode, expr); |
203 | 0 | Reference< XNode > aNode = aList->item(0); |
204 | 0 | return aNode; |
205 | 0 | } |
206 | | |
207 | | /** |
208 | | * Same as selectSingleNode but registers all namespaces declared on |
209 | | * namespaceNode |
210 | | */ |
211 | | Reference< XNode > SAL_CALL CXPathAPI::selectSingleNodeNS( |
212 | | const Reference< XNode >& contextNode, |
213 | | const OUString& expr, |
214 | | const Reference< XNode >& namespaceNode ) |
215 | 0 | { |
216 | 0 | lcl_collectRegisterNamespaces(*this, namespaceNode); |
217 | 0 | return selectSingleNode(contextNode, expr); |
218 | 0 | } |
219 | | |
220 | | static OUString make_error_message(const xmlError* pError) |
221 | 0 | { |
222 | 0 | OUStringBuffer buf; |
223 | 0 | if (pError) { |
224 | 0 | if (pError->message) { |
225 | 0 | buf.appendAscii(pError->message); |
226 | 0 | } |
227 | 0 | int line = pError->line; |
228 | 0 | if (line) { |
229 | 0 | buf.append("Line: " + OUString::number(static_cast<sal_Int32>(line)) + "\n"); |
230 | 0 | } |
231 | 0 | int column = pError->int2; |
232 | 0 | if (column) { |
233 | 0 | buf.append("Column: " + OUString::number(static_cast<sal_Int32>(column)) + "\n"); |
234 | 0 | } |
235 | 0 | } else { |
236 | 0 | buf.append("no error argument!"); |
237 | 0 | } |
238 | 0 | OUString msg = buf.makeStringAndClear(); |
239 | 0 | return msg; |
240 | 0 | } |
241 | | |
242 | | extern "C" { |
243 | | |
244 | | #if defined __GNUC__ |
245 | | __attribute__ ((format (printf, 2, 3))) |
246 | | #endif |
247 | | static void generic_error_func(void *, const char *format, ...) |
248 | 0 | { |
249 | 0 | char str[1000]; |
250 | 0 | va_list args; |
251 | |
|
252 | 0 | va_start(args, format); |
253 | | #ifdef _WIN32 |
254 | | #define vsnprintf _vsnprintf |
255 | | #endif |
256 | 0 | vsnprintf(str, sizeof(str), format, args); |
257 | 0 | va_end(args); |
258 | |
|
259 | 0 | SAL_WARN("unoxml", "libxml2 error: " << str); |
260 | 0 | } |
261 | | |
262 | | #if LIBXML_VERSION >= 21200 |
263 | | static void structured_error_func(void *, const xmlError* error) |
264 | | #else |
265 | | static void structured_error_func(void *, xmlErrorPtr error) |
266 | | #endif |
267 | 0 | { |
268 | 0 | SAL_WARN("unoxml", "libxml2 error: " << make_error_message(error)); |
269 | 0 | } |
270 | | |
271 | | } // extern "C" |
272 | | |
273 | | /** |
274 | | * evaluates an XPath string. relative XPath expressions are evaluated relative to |
275 | | * the context Node |
276 | | */ |
277 | | Reference< XXPathObject > SAL_CALL CXPathAPI::eval( |
278 | | Reference< XNode > const& xContextNode, |
279 | | const OUString& expr) |
280 | 721 | { |
281 | 721 | if (!xContextNode.is()) { throw RuntimeException(u"xContextNode does not exist!"_ustr); } |
282 | | |
283 | 721 | nsmap_t nsmap; |
284 | 721 | extensions_t extensions; |
285 | | |
286 | 721 | { |
287 | 721 | std::scoped_lock const g(m_Mutex); |
288 | 721 | nsmap = m_nsmap; |
289 | 721 | extensions = m_extensions; |
290 | 721 | } |
291 | | |
292 | | // get the node and document |
293 | 721 | ::rtl::Reference<DOM::CDocument> const pCDoc( |
294 | 721 | dynamic_cast<DOM::CDocument*>(xContextNode->getOwnerDocument().get())); |
295 | 721 | if (!pCDoc.is()) { throw RuntimeException(u"Interface pointer for the owner document of the xContextNode does not exist."_ustr); } |
296 | | |
297 | 721 | DOM::CNode *const pCNode = dynamic_cast<DOM::CNode*>(xContextNode.get()); |
298 | 721 | if (!pCNode) { throw RuntimeException(u"xContextNode interface pointer does not exist."_ustr); } |
299 | | |
300 | 721 | ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document! |
301 | | |
302 | 721 | xmlNodePtr const pNode = pCNode->GetNodePtr(); |
303 | 721 | if (!pNode) { throw RuntimeException(u"Node pointer for xContextNode does not exist."_ustr); } |
304 | 721 | xmlDocPtr pDoc = pNode->doc; |
305 | | |
306 | | /* NB: workaround for #i87252#: |
307 | | libxml < 2.6.17 considers it an error if the context |
308 | | node is the empty document (i.e. its xpathCtx->doc has no |
309 | | children). libxml 2.6.17 does not consider it an error. |
310 | | Unfortunately, old libxml prints an error message to stderr, |
311 | | which (afaik) cannot be turned off in this case, so we handle it. |
312 | | */ |
313 | 721 | if (!pDoc->children) { |
314 | 0 | throw XPathException(); |
315 | 0 | } |
316 | | |
317 | | /* Create xpath evaluation context */ |
318 | 721 | std::shared_ptr<xmlXPathContext> const xpathCtx( |
319 | 721 | xmlXPathNewContext(pDoc), xmlXPathFreeContext); |
320 | 721 | if (xpathCtx == nullptr) { throw XPathException(); } |
321 | | |
322 | | // set context node |
323 | 721 | xpathCtx->node = pNode; |
324 | | // error handling |
325 | 721 | xpathCtx->error = structured_error_func; |
326 | 721 | xmlSetGenericErrorFunc(nullptr, generic_error_func); |
327 | | |
328 | | // register namespaces and extension |
329 | 721 | lcl_registerNamespaces(xpathCtx.get(), nsmap); |
330 | 721 | lcl_registerExtensions(xpathCtx.get(), extensions); |
331 | | |
332 | | /* run the query */ |
333 | 721 | OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8); |
334 | 721 | xmlChar const *pStr = reinterpret_cast<xmlChar const *>(o1.getStr()); |
335 | 721 | std::shared_ptr<xmlXPathObject> const xpathObj( |
336 | 721 | xmlXPathEval(pStr, xpathCtx.get()), xmlXPathFreeObject); |
337 | 721 | xmlSetGenericErrorFunc(nullptr, nullptr); |
338 | 721 | if (nullptr == xpathObj) { |
339 | | // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message); |
340 | 0 | throw XPathException(); |
341 | 0 | } |
342 | 721 | Reference<XXPathObject> const xObj( |
343 | 721 | new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj)); |
344 | 721 | return xObj; |
345 | 721 | } |
346 | | |
347 | | /** |
348 | | * same as eval but registers all namespace declarations found on namespaceNode |
349 | | */ |
350 | | Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS( |
351 | | const Reference< XNode >& contextNode, |
352 | | const OUString& expr, |
353 | | const Reference< XNode >& namespaceNode) |
354 | 0 | { |
355 | 0 | lcl_collectRegisterNamespaces(*this, namespaceNode); |
356 | 0 | return eval(contextNode, expr); |
357 | 0 | } |
358 | | |
359 | | /** |
360 | | * uses the service manager to create an instance of the service denoted by aName. |
361 | | * If the returned object implements the XXPathExtension interface, it is added to the list |
362 | | * of extensions that are used when evaluating XPath strings with this XPathAPI instance |
363 | | */ |
364 | | void SAL_CALL CXPathAPI::registerExtension( |
365 | | const OUString& aName) |
366 | 0 | { |
367 | 0 | std::scoped_lock const g(m_Mutex); |
368 | | |
369 | | // get extension from service manager |
370 | 0 | Reference< XXPathExtension > const xExtension( |
371 | 0 | m_xContext->getServiceManager()->createInstanceWithContext(aName, m_xContext), UNO_QUERY_THROW); |
372 | 0 | m_extensions.push_back(xExtension); |
373 | 0 | } |
374 | | |
375 | | /** |
376 | | * registers the given extension instance to be used by XPath evaluations performed through this |
377 | | * XPathAPI instance |
378 | | */ |
379 | | void SAL_CALL CXPathAPI::registerExtensionInstance( |
380 | | Reference< XXPathExtension> const& xExtension) |
381 | 0 | { |
382 | 0 | if (!xExtension.is()) { |
383 | 0 | throw RuntimeException(u"Extension instance xExtension to be used by XPath does not exist."_ustr); |
384 | 0 | } |
385 | 0 | std::scoped_lock const g(m_Mutex); |
386 | 0 | m_extensions.push_back( xExtension ); |
387 | 0 | } |
388 | | } |
389 | | |
390 | | extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* |
391 | | unoxml_CXPathAPI_get_implementation( |
392 | | css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) |
393 | 515 | { |
394 | 515 | return cppu::acquire(new XPath::CXPathAPI(context)); |
395 | 515 | } |
396 | | |
397 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |