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