Line data Source code
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 :
30 : #include <rtl/ustrbuf.hxx>
31 :
32 : #include <nodelist.hxx>
33 : #include <xpathobject.hxx>
34 :
35 : #include "../dom/node.hxx"
36 : #include "../dom/document.hxx"
37 :
38 :
39 : using ::com::sun::star::lang::XMultiServiceFactory;
40 :
41 :
42 : namespace XPath
43 : {
44 : // factory
45 1158 : Reference< XInterface > CXPathAPI::_getInstance(const Reference< XMultiServiceFactory >& rSMgr)
46 : {
47 1158 : return Reference< XInterface >(static_cast<XXPathAPI*>(new CXPathAPI(rSMgr)));
48 : }
49 :
50 : // ctor
51 1158 : CXPathAPI::CXPathAPI(const Reference< XMultiServiceFactory >& rSMgr)
52 1158 : : m_aFactory(rSMgr)
53 : {
54 1158 : }
55 :
56 : const char* CXPathAPI::aImplementationName = "com.sun.star.comp.xml.xpath.XPathAPI";
57 : const char* CXPathAPI::aSupportedServiceNames[] = {
58 : "com.sun.star.xml.xpath.XPathAPI",
59 : NULL
60 : };
61 :
62 32 : OUString CXPathAPI::_getImplementationName()
63 : {
64 32 : return OUString::createFromAscii(aImplementationName);
65 : }
66 :
67 16 : Sequence<OUString> CXPathAPI::_getSupportedServiceNames()
68 : {
69 16 : Sequence<OUString> aSequence;
70 32 : for (int i=0; aSupportedServiceNames[i]!=NULL; i++) {
71 16 : aSequence.realloc(i+1);
72 16 : aSequence[i]=(OUString::createFromAscii(aSupportedServiceNames[i]));
73 : }
74 16 : return aSequence;
75 : }
76 :
77 0 : Sequence< OUString > SAL_CALL CXPathAPI::getSupportedServiceNames()
78 : throw (RuntimeException)
79 : {
80 0 : return CXPathAPI::_getSupportedServiceNames();
81 : }
82 :
83 0 : OUString SAL_CALL CXPathAPI::getImplementationName()
84 : throw (RuntimeException)
85 : {
86 0 : return CXPathAPI::_getImplementationName();
87 : }
88 :
89 0 : sal_Bool SAL_CALL CXPathAPI::supportsService(const OUString& aServiceName)
90 : throw (RuntimeException)
91 : {
92 0 : Sequence< OUString > supported = CXPathAPI::_getSupportedServiceNames();
93 0 : for (sal_Int32 i=0; i<supported.getLength(); i++)
94 : {
95 0 : if (supported[i] == aServiceName) return sal_True;
96 : }
97 0 : return sal_False;
98 : }
99 :
100 : // -------------------------------------------------------------------
101 :
102 4632 : void SAL_CALL CXPathAPI::registerNS(
103 : const OUString& aPrefix,
104 : const OUString& aURI)
105 : throw (RuntimeException)
106 : {
107 4632 : ::osl::MutexGuard const g(m_Mutex);
108 :
109 4632 : m_nsmap.insert(nsmap_t::value_type(aPrefix, aURI));
110 4632 : }
111 :
112 0 : void SAL_CALL CXPathAPI::unregisterNS(
113 : const OUString& aPrefix,
114 : const OUString& aURI)
115 : throw (RuntimeException)
116 : {
117 0 : ::osl::MutexGuard const g(m_Mutex);
118 :
119 0 : if ((m_nsmap.find(aPrefix))->second.equals(aURI)) {
120 0 : m_nsmap.erase(aPrefix);
121 0 : }
122 0 : }
123 :
124 : // register all namespaces stored in the namespace list for this object
125 : // with the current xpath evaluation context
126 22105 : static void lcl_registerNamespaces(
127 : xmlXPathContextPtr ctx,
128 : const nsmap_t& nsmap)
129 : {
130 22105 : nsmap_t::const_iterator i = nsmap.begin();
131 22105 : OString oprefix, ouri;
132 : xmlChar *p, *u;
133 132630 : while (i != nsmap.end())
134 : {
135 88420 : oprefix = OUStringToOString(i->first, RTL_TEXTENCODING_UTF8);
136 88420 : ouri = OUStringToOString(i->second, RTL_TEXTENCODING_UTF8);
137 88420 : p = (xmlChar*)oprefix.getStr();
138 88420 : u = (xmlChar*)ouri.getStr();
139 88420 : xmlXPathRegisterNs(ctx, p, u);
140 88420 : ++i;
141 22105 : }
142 22105 : }
143 :
144 : // get all ns decls on a node (and parent nodes, if any)
145 0 : static void lcl_collectNamespaces(
146 : nsmap_t & rNamespaces, Reference< XNode > const& xNamespaceNode)
147 : {
148 0 : DOM::CNode *const pCNode(DOM::CNode::GetImplementation(xNamespaceNode));
149 0 : if (!pCNode) { throw RuntimeException(); }
150 :
151 0 : ::osl::MutexGuard const g(pCNode->GetOwnerDocument().GetMutex());
152 :
153 0 : xmlNodePtr pNode = pCNode->GetNodePtr();
154 0 : while (pNode != 0) {
155 0 : xmlNsPtr curDef = pNode->nsDef;
156 0 : while (curDef != 0) {
157 0 : const xmlChar* xHref = curDef->href;
158 0 : OUString aURI((sal_Char*)xHref, strlen((char*)xHref), RTL_TEXTENCODING_UTF8);
159 0 : const xmlChar* xPre = curDef->prefix;
160 0 : OUString aPrefix((sal_Char*)xPre, strlen((char*)xPre), RTL_TEXTENCODING_UTF8);
161 : // we could already have this prefix from a child node
162 0 : if (rNamespaces.find(aPrefix) == rNamespaces.end())
163 : {
164 0 : rNamespaces.insert(::std::make_pair(aPrefix, aURI));
165 : }
166 0 : curDef = curDef->next;
167 0 : }
168 0 : pNode = pNode->parent;
169 0 : }
170 0 : }
171 :
172 0 : static void lcl_collectRegisterNamespaces(
173 : CXPathAPI & rAPI, Reference< XNode > const& xNamespaceNode)
174 : {
175 0 : nsmap_t namespaces;
176 0 : lcl_collectNamespaces(namespaces, xNamespaceNode);
177 0 : for (nsmap_t::const_iterator iter = namespaces.begin();
178 0 : iter != namespaces.end(); ++iter)
179 : {
180 0 : rAPI.registerNS(iter->first, iter->second);
181 0 : }
182 0 : }
183 :
184 : // register function and variable lookup functions with the current
185 : // xpath evaluation context
186 22105 : static void lcl_registerExtensions(
187 : xmlXPathContextPtr ctx,
188 : const extensions_t& extensions)
189 : {
190 22105 : extensions_t::const_iterator i = extensions.begin();
191 44210 : while (i != extensions.end())
192 : {
193 0 : Libxml2ExtensionHandle aHandle = (*i)->getLibxml2ExtensionHandle();
194 0 : if ( aHandle.functionLookupFunction != 0 )
195 : {
196 : xmlXPathRegisterFuncLookup(ctx,
197 : reinterpret_cast<xmlXPathFuncLookupFunc>(
198 0 : sal::static_int_cast<sal_IntPtr>(aHandle.functionLookupFunction)),
199 : reinterpret_cast<void*>(
200 0 : sal::static_int_cast<sal_IntPtr>(aHandle.functionData)));
201 : }
202 0 : if ( aHandle.variableLookupFunction != 0 )
203 : {
204 : xmlXPathRegisterVariableLookup(ctx,
205 : reinterpret_cast<xmlXPathVariableLookupFunc>(
206 0 : sal::static_int_cast<sal_IntPtr>(aHandle.variableLookupFunction)),
207 : reinterpret_cast<void*>(
208 0 : sal::static_int_cast<sal_IntPtr>(aHandle.variableData)));
209 : }
210 0 : ++i;
211 : }
212 22105 : }
213 :
214 : /**
215 : * Use an XPath string to select a nodelist.
216 : */
217 23160 : Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeList(
218 : const Reference< XNode >& contextNode,
219 : const OUString& expr)
220 : throw (RuntimeException, XPathException)
221 : {
222 23160 : Reference< XXPathObject > xobj = eval(contextNode, expr);
223 22105 : return xobj->getNodeList();
224 : }
225 :
226 : /**
227 : * same as selectNodeList but registers all name space decalratiosn found on namespaceNode
228 : */
229 0 : Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeListNS(
230 : const Reference< XNode >& contextNode,
231 : const OUString& expr,
232 : const Reference< XNode >& namespaceNode)
233 : throw (RuntimeException, XPathException)
234 : {
235 0 : lcl_collectRegisterNamespaces(*this, namespaceNode);
236 0 : return selectNodeList(contextNode, expr);
237 : }
238 :
239 : /**
240 : * Same as selectNodeList but returns the first node (if any)
241 : */
242 20844 : Reference< XNode > SAL_CALL CXPathAPI::selectSingleNode(
243 : const Reference< XNode >& contextNode,
244 : const OUString& expr)
245 : throw (RuntimeException, XPathException)
246 : {
247 20844 : Reference< XNodeList > aList = selectNodeList(contextNode, expr);
248 19789 : Reference< XNode > aNode = aList->item(0);
249 19789 : return aNode;
250 : }
251 :
252 : /**
253 : * Same as selectSingleNode but registers all namespaces declared on
254 : * namespaceNode
255 : */
256 0 : Reference< XNode > SAL_CALL CXPathAPI::selectSingleNodeNS(
257 : const Reference< XNode >& contextNode,
258 : const OUString& expr,
259 : const Reference< XNode >& namespaceNode )
260 : throw (RuntimeException, XPathException)
261 : {
262 0 : lcl_collectRegisterNamespaces(*this, namespaceNode);
263 0 : return selectSingleNode(contextNode, expr);
264 : }
265 :
266 0 : static OUString make_error_message(xmlErrorPtr pError)
267 : {
268 0 : ::rtl::OUStringBuffer buf;
269 0 : if (pError->message) {
270 0 : buf.appendAscii(pError->message);
271 : }
272 0 : int line = pError->line;
273 0 : if (line) {
274 0 : buf.appendAscii("Line: ");
275 0 : buf.append(static_cast<sal_Int32>(line));
276 0 : buf.appendAscii("\n");
277 : }
278 0 : int column = pError->int2;
279 0 : if (column) {
280 0 : buf.appendAscii("Column: ");
281 0 : buf.append(static_cast<sal_Int32>(column));
282 0 : buf.appendAscii("\n");
283 : }
284 0 : OUString msg = buf.makeStringAndClear();
285 0 : return msg;
286 : }
287 :
288 : extern "C" {
289 :
290 0 : static void generic_error_func(void *userData, const char *format, ...)
291 : {
292 : (void) userData;
293 : char str[1000];
294 : va_list args;
295 :
296 0 : va_start(args, format);
297 : #ifdef _WIN32
298 : #define vsnprintf _vsnprintf
299 : #endif
300 0 : vsnprintf(str, sizeof(str), format, args);
301 0 : va_end(args);
302 :
303 : ::rtl::OUStringBuffer buf(
304 0 : "libxml2 error:\n");
305 0 : buf.appendAscii(str);
306 : OString msg = OUStringToOString(buf.makeStringAndClear(),
307 0 : RTL_TEXTENCODING_ASCII_US);
308 0 : OSL_FAIL(msg.getStr());
309 0 : }
310 :
311 0 : static void structured_error_func(void * userData, xmlErrorPtr error)
312 : {
313 : (void) userData;
314 : ::rtl::OUStringBuffer buf(
315 0 : "libxml2 error:\n");
316 0 : if (error) {
317 0 : buf.append(make_error_message(error));
318 : } else {
319 0 : buf.append("no error argument!");
320 : }
321 : OString msg = OUStringToOString(buf.makeStringAndClear(),
322 0 : RTL_TEXTENCODING_ASCII_US);
323 0 : OSL_FAIL(msg.getStr());
324 0 : }
325 :
326 : } // extern "C"
327 :
328 : /**
329 : * evaluates an XPath string. relative XPath expressions are evaluated relative to
330 : * the context Node
331 : */
332 23160 : Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
333 : Reference< XNode > const& xContextNode,
334 : const OUString& expr)
335 : throw (RuntimeException, XPathException)
336 : {
337 23160 : if (!xContextNode.is()) { throw RuntimeException(); }
338 :
339 23160 : nsmap_t nsmap;
340 23160 : extensions_t extensions;
341 :
342 : {
343 23160 : ::osl::MutexGuard const g(m_Mutex);
344 23160 : nsmap = m_nsmap;
345 23160 : extensions = m_extensions;
346 : }
347 :
348 : // get the node and document
349 : ::rtl::Reference<DOM::CDocument> const pCDoc(
350 : dynamic_cast<DOM::CDocument*>( DOM::CNode::GetImplementation(
351 23160 : xContextNode->getOwnerDocument())));
352 23160 : if (!pCDoc.is()) { throw RuntimeException(); }
353 :
354 23160 : DOM::CNode *const pCNode = DOM::CNode::GetImplementation(xContextNode);
355 23160 : if (!pCNode) { throw RuntimeException(); }
356 :
357 23160 : ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document!
358 :
359 23160 : xmlNodePtr const pNode = pCNode->GetNodePtr();
360 23160 : if (!pNode) { throw RuntimeException(); }
361 23160 : xmlDocPtr pDoc = pNode->doc;
362 :
363 : /* NB: workaround for #i87252#:
364 : libxml < 2.6.17 considers it an error if the context
365 : node is the empty document (i.e. its xpathCtx->doc has no
366 : children). libxml 2.6.17 does not consider it an error.
367 : Unfortunately, old libxml prints an error message to stderr,
368 : which (afaik) cannot be turned off in this case, so we handle it.
369 : */
370 23160 : if (!pDoc->children) {
371 1055 : throw XPathException();
372 : }
373 :
374 : /* Create xpath evaluation context */
375 : ::boost::shared_ptr<xmlXPathContext> const xpathCtx(
376 22105 : xmlXPathNewContext(pDoc), xmlXPathFreeContext);
377 22105 : if (xpathCtx == NULL) { throw XPathException(); }
378 :
379 : // set context node
380 22105 : xpathCtx->node = pNode;
381 : // error handling
382 22105 : xpathCtx->error = structured_error_func;
383 22105 : xmlSetGenericErrorFunc(NULL, generic_error_func);
384 :
385 : // register namespaces and extension
386 22105 : lcl_registerNamespaces(xpathCtx.get(), nsmap);
387 22105 : lcl_registerExtensions(xpathCtx.get(), extensions);
388 :
389 : /* run the query */
390 22105 : OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8);
391 22105 : xmlChar *xStr = (xmlChar*)o1.getStr();
392 : ::boost::shared_ptr<xmlXPathObject> const xpathObj(
393 22105 : xmlXPathEval(xStr, xpathCtx.get()), xmlXPathFreeObject);
394 22105 : xmlSetGenericErrorFunc(NULL, NULL);
395 22105 : if (0 == xpathObj) {
396 : // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
397 0 : throw XPathException();
398 : }
399 : Reference<XXPathObject> const xObj(
400 22105 : new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj));
401 22105 : return xObj;
402 : }
403 :
404 : /**
405 : * same as eval but registers all namespace declarations found on namespaceNode
406 : */
407 0 : Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS(
408 : const Reference< XNode >& contextNode,
409 : const OUString& expr,
410 : const Reference< XNode >& namespaceNode)
411 : throw (RuntimeException, XPathException)
412 : {
413 0 : lcl_collectRegisterNamespaces(*this, namespaceNode);
414 0 : return eval(contextNode, expr);
415 : }
416 :
417 : /**
418 : * uses the service manager to create an instance of the service denoted by aName.
419 : * If the returned object implements the XXPathExtension interface, it is added to the list
420 : * of extensions that are used when evaluating XPath strings with this XPathAPI instance
421 : */
422 0 : void SAL_CALL CXPathAPI::registerExtension(
423 : const OUString& aName)
424 : throw (RuntimeException)
425 : {
426 0 : ::osl::MutexGuard const g(m_Mutex);
427 :
428 : // get extension from service manager
429 : Reference< XXPathExtension > const xExtension(
430 0 : m_aFactory->createInstance(aName), UNO_QUERY_THROW);
431 0 : m_extensions.push_back(xExtension);
432 0 : }
433 :
434 : /**
435 : * registers the given extension instance to be used by XPath evaluations performed through this
436 : * XPathAPI instance
437 : */
438 0 : void SAL_CALL CXPathAPI::registerExtensionInstance(
439 : Reference< XXPathExtension> const& xExtension)
440 : throw (RuntimeException)
441 : {
442 0 : if (!xExtension.is()) {
443 0 : throw RuntimeException();
444 : }
445 0 : ::osl::MutexGuard const g(m_Mutex);
446 0 : m_extensions.push_back( xExtension );
447 0 : }
448 : }
449 :
450 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|