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