Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / unoxml / source / xpath / xpathapi.cxx
blob17b28027534c9f0bab0b5dd06c8b5703c5a4780b
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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"
22 #include <stdarg.h>
23 #include <string.h>
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"
37 #include <node.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;
48 namespace XPath
50 // ctor
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,
73 const OUString& aURI)
75 std::scoped_lock const g(m_Mutex);
77 m_nsmap.emplace(aPrefix, aURI);
80 void SAL_CALL CXPathAPI::unregisterNS(
81 const OUString& aPrefix,
82 const OUString& aURI)
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,
95 const nsmap_t& nsmap)
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)
136 nsmap_t namespaces;
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);
204 return aNode;
208 * Same as selectSingleNode but registers all namespaces declared on
209 * namespaceNode
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)
222 OUStringBuffer buf;
223 if (pError) {
224 if (pError->message) {
225 buf.appendAscii(pError->message);
227 int line = pError->line;
228 if (line) {
229 buf.append("Line: " + OUString::number(static_cast<sal_Int32>(line)) + "\n");
231 int column = pError->int2;
232 if (column) {
233 buf.append("Column: " + OUString::number(static_cast<sal_Int32>(column)) + "\n");
235 } else {
236 buf.append("no error argument!");
238 OUString msg = buf.makeStringAndClear();
239 return msg;
242 extern "C" {
244 #if defined __GNUC__
245 __attribute__ ((format (printf, 2, 3)))
246 #endif
247 static void generic_error_func(void *, const char *format, ...)
249 char str[1000];
250 va_list args;
252 va_start(args, format);
253 #ifdef _WIN32
254 #define vsnprintf _vsnprintf
255 #endif
256 vsnprintf(str, sizeof(str), format, args);
257 va_end(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));
267 } // extern "C"
270 * evaluates an XPath string. relative XPath expressions are evaluated relative to
271 * the context Node
273 Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
274 Reference< XNode > const& xContextNode,
275 const OUString& expr)
277 if (!xContextNode.is()) { throw RuntimeException(); }
279 nsmap_t nsmap;
280 extensions_t extensions;
283 std::scoped_lock const g(m_Mutex);
284 nsmap = m_nsmap;
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(); }
318 // set context node
319 xpathCtx->node = pNode;
320 // error handling
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);
328 /* run the query */
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));
340 return xObj;
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
373 * XPathAPI instance
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: */