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