xref: /AOO41X/main/unoxml/source/xpath/xpathapi.cxx (revision 60c8d5168e5f7dc5d422d1593ef0d6e214f40a3d)
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
_getInstance(const Reference<XMultiServiceFactory> & rSMgr)49     Reference< XInterface > CXPathAPI::_getInstance(const Reference< XMultiServiceFactory >& rSMgr)
50     {
51         return Reference< XInterface >(static_cast<XXPathAPI*>(new CXPathAPI(rSMgr)));
52     }
53 
54     // ctor
CXPathAPI(const Reference<XMultiServiceFactory> & rSMgr)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 
_getImplementationName()66     OUString CXPathAPI::_getImplementationName()
67     {
68         return OUString::createFromAscii(aImplementationName);
69     }
70 
_getSupportedServiceNames()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 
getSupportedServiceNames()81     Sequence< OUString > SAL_CALL CXPathAPI::getSupportedServiceNames()
82         throw (RuntimeException)
83     {
84         return CXPathAPI::_getSupportedServiceNames();
85     }
86 
getImplementationName()87     OUString SAL_CALL CXPathAPI::getImplementationName()
88         throw (RuntimeException)
89     {
90         return CXPathAPI::_getImplementationName();
91     }
92 
supportsService(const OUString & aServiceName)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 
registerNS(const OUString & aPrefix,const OUString & aURI)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 
unregisterNS(const OUString & aPrefix,const OUString & aURI)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
lcl_registerNamespaces(xmlXPathContextPtr ctx,const nsmap_t & nsmap)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)
lcl_collectNamespaces(nsmap_t & rNamespaces,Reference<XNode> const & xNamespaceNode)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 
lcl_collectRegisterNamespaces(CXPathAPI & rAPI,Reference<XNode> const & xNamespaceNode)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
lcl_registerExtensions(xmlXPathContextPtr ctx,const extensions_t & extensions)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      */
selectNodeList(const Reference<XNode> & contextNode,const OUString & expr)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      */
selectNodeListNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)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      */
selectSingleNode(const Reference<XNode> & contextNode,const OUString & expr)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      */
selectSingleNodeNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)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
make_error_message(const xmlError * pError)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 
generic_error_func(void * userData,const char * format,...)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
structured_error_func(void * userData,const xmlError * error)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      */
eval(Reference<XNode> const & xContextNode,const OUString & expr)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      */
evalNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)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      */
registerExtension(const OUString & aName)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      */
registerExtensionInstance(Reference<XXPathExtension> const & xExtension)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