Bump version to 21.06.18.1
[LibreOffice.git] / unoxml / source / xpath / xpathapi.cxx
blobd6698c98a117c0c425e044da8f547139d4db11fa
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 ::osl::MutexGuard 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 ::osl::MutexGuard 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(comphelper::getUnoTunnelImplementation<DOM::CNode>(xNamespaceNode));
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: ");
230 buf.append(static_cast<sal_Int32>(line));
231 buf.append("\n");
233 int column = pError->int2;
234 if (column) {
235 buf.append("Column: ");
236 buf.append(static_cast<sal_Int32>(column));
237 buf.append("\n");
239 } else {
240 buf.append("no error argument!");
242 OUString msg = buf.makeStringAndClear();
243 return msg;
246 extern "C" {
248 #if defined __GNUC__
249 __attribute__ ((format (printf, 2, 3)))
250 #endif
251 static void generic_error_func(void *, const char *format, ...)
253 char str[1000];
254 va_list args;
256 va_start(args, format);
257 #ifdef _WIN32
258 #define vsnprintf _vsnprintf
259 #endif
260 vsnprintf(str, sizeof(str), format, args);
261 va_end(args);
263 SAL_WARN("unoxml", "libxml2 error: " << str);
266 static void structured_error_func(void *, xmlErrorPtr error)
268 SAL_WARN("unoxml", "libxml2 error: " << make_error_message(error));
271 } // extern "C"
274 * evaluates an XPath string. relative XPath expressions are evaluated relative to
275 * the context Node
277 Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
278 Reference< XNode > const& xContextNode,
279 const OUString& expr)
281 if (!xContextNode.is()) { throw RuntimeException(); }
283 nsmap_t nsmap;
284 extensions_t extensions;
287 ::osl::MutexGuard const g(m_Mutex);
288 nsmap = m_nsmap;
289 extensions = m_extensions;
292 // get the node and document
293 ::rtl::Reference<DOM::CDocument> const pCDoc(
294 dynamic_cast<DOM::CDocument*>( comphelper::getUnoTunnelImplementation<DOM::CNode>(
295 xContextNode->getOwnerDocument())));
296 if (!pCDoc.is()) { throw RuntimeException(); }
298 DOM::CNode *const pCNode = comphelper::getUnoTunnelImplementation<DOM::CNode>(xContextNode);
299 if (!pCNode) { throw RuntimeException(); }
301 ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document!
303 xmlNodePtr const pNode = pCNode->GetNodePtr();
304 if (!pNode) { throw RuntimeException(); }
305 xmlDocPtr pDoc = pNode->doc;
307 /* NB: workaround for #i87252#:
308 libxml < 2.6.17 considers it an error if the context
309 node is the empty document (i.e. its xpathCtx->doc has no
310 children). libxml 2.6.17 does not consider it an error.
311 Unfortunately, old libxml prints an error message to stderr,
312 which (afaik) cannot be turned off in this case, so we handle it.
314 if (!pDoc->children) {
315 throw XPathException();
318 /* Create xpath evaluation context */
319 std::shared_ptr<xmlXPathContext> const xpathCtx(
320 xmlXPathNewContext(pDoc), xmlXPathFreeContext);
321 if (xpathCtx == nullptr) { throw XPathException(); }
323 // set context node
324 xpathCtx->node = pNode;
325 // error handling
326 xpathCtx->error = structured_error_func;
327 xmlSetGenericErrorFunc(nullptr, generic_error_func);
329 // register namespaces and extension
330 lcl_registerNamespaces(xpathCtx.get(), nsmap);
331 lcl_registerExtensions(xpathCtx.get(), extensions);
333 /* run the query */
334 OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8);
335 xmlChar const *pStr = reinterpret_cast<xmlChar const *>(o1.getStr());
336 std::shared_ptr<xmlXPathObject> const xpathObj(
337 xmlXPathEval(pStr, xpathCtx.get()), xmlXPathFreeObject);
338 xmlSetGenericErrorFunc(nullptr, nullptr);
339 if (nullptr == xpathObj) {
340 // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
341 throw XPathException();
343 Reference<XXPathObject> const xObj(
344 new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj));
345 return xObj;
349 * same as eval but registers all namespace declarations found on namespaceNode
351 Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS(
352 const Reference< XNode >& contextNode,
353 const OUString& expr,
354 const Reference< XNode >& namespaceNode)
356 lcl_collectRegisterNamespaces(*this, namespaceNode);
357 return eval(contextNode, expr);
361 * uses the service manager to create an instance of the service denoted by aName.
362 * If the returned object implements the XXPathExtension interface, it is added to the list
363 * of extensions that are used when evaluating XPath strings with this XPathAPI instance
365 void SAL_CALL CXPathAPI::registerExtension(
366 const OUString& aName)
368 ::osl::MutexGuard const g(m_Mutex);
370 // get extension from service manager
371 Reference< XXPathExtension > const xExtension(
372 m_xContext->getServiceManager()->createInstanceWithContext(aName, m_xContext), UNO_QUERY_THROW);
373 m_extensions.push_back(xExtension);
377 * registers the given extension instance to be used by XPath evaluations performed through this
378 * XPathAPI instance
380 void SAL_CALL CXPathAPI::registerExtensionInstance(
381 Reference< XXPathExtension> const& xExtension)
383 if (!xExtension.is()) {
384 throw RuntimeException();
386 ::osl::MutexGuard const g(m_Mutex);
387 m_extensions.push_back( xExtension );
391 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
392 unoxml_CXPathAPI_get_implementation(
393 css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&)
395 return cppu::acquire(new XPath::CXPathAPI(context));
398 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */