xref: /aoo42x/main/unoxml/source/xpath/xpathapi.cxx (revision e9cbe144)
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     static OUString make_error_message(xmlErrorPtr pError)
271     {
272         ::rtl::OUStringBuffer buf;
273         if (pError->message) {
274             buf.appendAscii(pError->message);
275         }
276         int line = pError->line;
277         if (line) {
278             buf.appendAscii("Line: ");
279             buf.append(static_cast<sal_Int32>(line));
280             buf.appendAscii("\n");
281         }
282         int column = pError->int2;
283         if (column) {
284             buf.appendAscii("Column: ");
285             buf.append(static_cast<sal_Int32>(column));
286             buf.appendAscii("\n");
287         }
288         OUString msg = buf.makeStringAndClear();
289         return msg;
290     }
291 
292     extern "C" {
293 
294         static void generic_error_func(void *userData, const char *format, ...)
295         {
296             (void) userData;
297             char str[1000];
298             va_list args;
299 
300             va_start(args, format);
301 #ifdef _WIN32
302 #define vsnprintf _vsnprintf
303 #endif
304             vsnprintf(str, sizeof(str), format, args);
305             va_end(args);
306 
307             ::rtl::OUStringBuffer buf(
308                 OUString::createFromAscii("libxml2 error:\n"));
309             buf.appendAscii(str);
310             OString msg = OUStringToOString(buf.makeStringAndClear(),
311                 RTL_TEXTENCODING_ASCII_US);
312             OSL_ENSURE(sal_False, msg.getStr());
313         }
314 
315         static void structured_error_func(void * userData, xmlErrorPtr error)
316         {
317             (void) userData;
318             ::rtl::OUStringBuffer buf(
319                 OUString::createFromAscii("libxml2 error:\n"));
320             if (error) {
321                 buf.append(make_error_message(error));
322             } else {
323                 buf.append(OUString::createFromAscii("no error argument!"));
324             }
325             OString msg = OUStringToOString(buf.makeStringAndClear(),
326                 RTL_TEXTENCODING_ASCII_US);
327             OSL_ENSURE(sal_False, msg.getStr());
328         }
329 
330     } // extern "C"
331 
332 	/**
333 	 * evaluates an XPath string. relative XPath expressions are evaluated relative to
334 	 * the context Node
335 	 */
336     Reference< XXPathObject > SAL_CALL CXPathAPI::eval(
337             Reference< XNode > const& xContextNode,
338 			const OUString& expr)
339         throw (RuntimeException, XPathException)
340     {
341         if (!xContextNode.is()) { throw RuntimeException(); }
342 
343         nsmap_t nsmap;
344         extensions_t extensions;
345 
346         {
347             ::osl::MutexGuard const g(m_Mutex);
348             nsmap = m_nsmap;
349             extensions = m_extensions;
350         }
351 
352         // get the node and document
353         ::rtl::Reference<DOM::CDocument> const pCDoc(
354                 dynamic_cast<DOM::CDocument*>( DOM::CNode::GetImplementation(
355                         xContextNode->getOwnerDocument())));
356         if (!pCDoc.is()) { throw RuntimeException(); }
357 
358         DOM::CNode *const pCNode = DOM::CNode::GetImplementation(xContextNode);
359         if (!pCNode) { throw RuntimeException(); }
360 
361         ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document!
362 
363         xmlNodePtr const pNode = pCNode->GetNodePtr();
364         if (!pNode) { throw RuntimeException(); }
365         xmlDocPtr pDoc = pNode->doc;
366 
367         /* NB: workaround for #i87252#:
368            libxml < 2.6.17 considers it an error if the context
369            node is the empty document (i.e. its xpathCtx->doc has no
370            children). libxml 2.6.17 does not consider it an error.
371            Unfortunately, old libxml prints an error message to stderr,
372            which (afaik) cannot be turned off in this case, so we handle it.
373         */
374         if (!pDoc->children) {
375             throw XPathException();
376         }
377 
378         /* Create xpath evaluation context */
379         ::boost::shared_ptr<xmlXPathContext> const xpathCtx(
380                 xmlXPathNewContext(pDoc), xmlXPathFreeContext);
381         if (xpathCtx == NULL) { throw XPathException(); }
382 
383         // set context node
384         xpathCtx->node = pNode;
385         // error handling
386         xpathCtx->error = structured_error_func;
387         xmlSetGenericErrorFunc(NULL, generic_error_func);
388 
389 		// register namespaces and extension
390         lcl_registerNamespaces(xpathCtx.get(), nsmap);
391         lcl_registerExtensions(xpathCtx.get(), extensions);
392 
393 		/* run the query */
394         OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8);
395         xmlChar *xStr = (xmlChar*)o1.getStr();
396         ::boost::shared_ptr<xmlXPathObject> const xpathObj(
397                 xmlXPathEval(xStr, xpathCtx.get()), xmlXPathFreeObject);
398         if (0 == xpathObj) {
399             // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message);
400             throw XPathException();
401         }
402         Reference<XXPathObject> const xObj(
403                 new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj));
404         return xObj;
405     }
406 
407 	/**
408 	 * same as eval but registers all namespace declarations found on namespaceNode
409 	 */
410     Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS(
411 			const Reference< XNode >& contextNode,
412 			const OUString& expr,
413 			const Reference< XNode >& namespaceNode)
414         throw (RuntimeException, XPathException)
415     {
416         lcl_collectRegisterNamespaces(*this, namespaceNode);
417         return eval(contextNode, expr);
418     }
419 
420 	/**
421 	 * uses the service manager to create an instance of the service denoted by aName.
422 	 * If the returned object implements the XXPathExtension interface, it is added to the list
423 	 * of extensions that are used when evaluating XPath strings with this XPathAPI instance
424 	 */
425     void SAL_CALL CXPathAPI::registerExtension(
426 			const OUString& aName)
427         throw (RuntimeException)
428     {
429         ::osl::MutexGuard const g(m_Mutex);
430 
431         // get extension from service manager
432         Reference< XXPathExtension > const xExtension(
433                 m_aFactory->createInstance(aName), UNO_QUERY_THROW);
434         m_extensions.push_back(xExtension);
435     }
436 
437 	/**
438 	 * registers the given extension instance to be used by XPath evaluations performed through this
439 	 * XPathAPI instance
440 	 */
441     void SAL_CALL CXPathAPI::registerExtensionInstance(
442             Reference< XXPathExtension> const& xExtension)
443         throw (RuntimeException)
444     {
445         if (!xExtension.is()) {
446             throw RuntimeException();
447         }
448         ::osl::MutexGuard const g(m_Mutex);
449         m_extensions.push_back( xExtension );
450     }
451 }
452