Bump version to 6.4-15
[LibreOffice.git] / unoxml / source / xpath / xpathapi.cxx
blob5d3e6d8c6189b4e0d220474b368b9d5787bd3b2e
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 <rtl/ustrbuf.hxx>
31 #include <sal/log.hxx>
33 #include "nodelist.hxx"
34 #include "xpathobject.hxx"
36 #include <node.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;
48 namespace XPath
50 // factory
51 Reference< XInterface > CXPathAPI::_getInstance(const Reference< XMultiServiceFactory >& rSMgr)
53 return static_cast<XXPathAPI*>(new CXPathAPI(rSMgr));
56 // ctor
57 CXPathAPI::CXPathAPI(const Reference< XMultiServiceFactory >& rSMgr)
58 : m_aFactory(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,
89 const OUString& aURI)
91 ::osl::MutexGuard const g(m_Mutex);
93 m_nsmap.emplace(aPrefix, aURI);
96 void SAL_CALL CXPathAPI::unregisterNS(
97 const OUString& aPrefix,
98 const OUString& aURI)
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)
152 nsmap_t namespaces;
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);
220 return aNode;
224 * Same as selectSingleNode but registers all namespaces declared on
225 * namespaceNode
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)
238 OUStringBuffer buf;
239 if (pError) {
240 if (pError->message) {
241 buf.appendAscii(pError->message);
243 int line = pError->line;
244 if (line) {
245 buf.append("Line: ");
246 buf.append(static_cast<sal_Int32>(line));
247 buf.append("\n");
249 int column = pError->int2;
250 if (column) {
251 buf.append("Column: ");
252 buf.append(static_cast<sal_Int32>(column));
253 buf.append("\n");
255 } else {
256 buf.append("no error argument!");
258 OUString msg = buf.makeStringAndClear();
259 return msg;
262 extern "C" {
264 #if defined __GNUC__
265 __attribute__ ((format (printf, 2, 3)))
266 #endif
267 static void generic_error_func(void *, const char *format, ...)
269 char str[1000];
270 va_list args;
272 va_start(args, format);
273 #ifdef _WIN32
274 #define vsnprintf _vsnprintf
275 #endif
276 vsnprintf(str, sizeof(str), format, args);
277 va_end(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));
287 } // extern "C"
290 * evaluates an XPath string. relative XPath expressions are evaluated relative to
291 * the context Node
293 Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
294 Reference< XNode > const& xContextNode,
295 const OUString& expr)
297 if (!xContextNode.is()) { throw RuntimeException(); }
299 nsmap_t nsmap;
300 extensions_t extensions;
303 ::osl::MutexGuard const g(m_Mutex);
304 nsmap = m_nsmap;
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(); }
339 // set context node
340 xpathCtx->node = pNode;
341 // error handling
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);
349 /* run the query */
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));
361 return xObj;
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
394 * XPathAPI instance
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: */