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 { 83 return CXPathAPI::_getSupportedServiceNames(); 84 } 85 getImplementationName()86 OUString SAL_CALL CXPathAPI::getImplementationName() 87 { 88 return CXPathAPI::_getImplementationName(); 89 } 90 supportsService(const OUString & aServiceName)91 sal_Bool SAL_CALL CXPathAPI::supportsService(const OUString& aServiceName) 92 { 93 Sequence< OUString > supported = CXPathAPI::_getSupportedServiceNames(); 94 for (sal_Int32 i=0; i<supported.getLength(); i++) 95 { 96 if (supported[i] == aServiceName) return sal_True; 97 } 98 return sal_False; 99 } 100 101 // ------------------------------------------------------------------- 102 registerNS(const OUString & aPrefix,const OUString & aURI)103 void SAL_CALL CXPathAPI::registerNS( 104 const OUString& aPrefix, 105 const OUString& aURI) 106 { 107 ::osl::MutexGuard const g(m_Mutex); 108 109 m_nsmap.insert(nsmap_t::value_type(aPrefix, aURI)); 110 } 111 unregisterNS(const OUString & aPrefix,const OUString & aURI)112 void SAL_CALL CXPathAPI::unregisterNS( 113 const OUString& aPrefix, 114 const OUString& aURI) 115 { 116 ::osl::MutexGuard const g(m_Mutex); 117 118 if ((m_nsmap.find(aPrefix))->second.equals(aURI)) { 119 m_nsmap.erase(aPrefix); 120 } 121 } 122 123 // register all namespaces stored in the namespace list for this object 124 // with the current xpath evaluation context lcl_registerNamespaces(xmlXPathContextPtr ctx,const nsmap_t & nsmap)125 static void lcl_registerNamespaces( 126 xmlXPathContextPtr ctx, 127 const nsmap_t& nsmap) 128 { 129 nsmap_t::const_iterator i = nsmap.begin(); 130 OString oprefix, ouri; 131 xmlChar *p, *u; 132 while (i != nsmap.end()) 133 { 134 oprefix = OUStringToOString(i->first, RTL_TEXTENCODING_UTF8); 135 ouri = OUStringToOString(i->second, RTL_TEXTENCODING_UTF8); 136 p = (xmlChar*)oprefix.getStr(); 137 u = (xmlChar*)ouri.getStr(); 138 xmlXPathRegisterNs(ctx, p, u); 139 i++; 140 } 141 } 142 143 // get all ns decls on a node (and parent nodes, if any) lcl_collectNamespaces(nsmap_t & rNamespaces,Reference<XNode> const & xNamespaceNode)144 static void lcl_collectNamespaces( 145 nsmap_t & rNamespaces, Reference< XNode > const& xNamespaceNode) 146 { 147 DOM::CNode *const pCNode(DOM::CNode::GetImplementation(xNamespaceNode)); 148 if (!pCNode) { throw RuntimeException(); } 149 150 ::osl::MutexGuard const g(pCNode->GetOwnerDocument().GetMutex()); 151 152 xmlNodePtr pNode = pCNode->GetNodePtr(); 153 while (pNode != 0) { 154 xmlNsPtr curDef = pNode->nsDef; 155 while (curDef != 0) { 156 const xmlChar* xHref = curDef->href; 157 OUString aURI((sal_Char*)xHref, strlen((char*)xHref), RTL_TEXTENCODING_UTF8); 158 const xmlChar* xPre = curDef->prefix; 159 OUString aPrefix((sal_Char*)xPre, strlen((char*)xPre), RTL_TEXTENCODING_UTF8); 160 // we could already have this prefix from a child node 161 if (rNamespaces.find(aPrefix) == rNamespaces.end()) 162 { 163 rNamespaces.insert(::std::make_pair(aPrefix, aURI)); 164 } 165 curDef = curDef->next; 166 } 167 pNode = pNode->parent; 168 } 169 } 170 lcl_collectRegisterNamespaces(CXPathAPI & rAPI,Reference<XNode> const & xNamespaceNode)171 static void lcl_collectRegisterNamespaces( 172 CXPathAPI & rAPI, Reference< XNode > const& xNamespaceNode) 173 { 174 nsmap_t namespaces; 175 lcl_collectNamespaces(namespaces, xNamespaceNode); 176 for (nsmap_t::const_iterator iter = namespaces.begin(); 177 iter != namespaces.end(); ++iter) 178 { 179 rAPI.registerNS(iter->first, iter->second); 180 } 181 } 182 183 // register function and variable lookup functions with the current 184 // xpath evaluation context lcl_registerExtensions(xmlXPathContextPtr ctx,const extensions_t & extensions)185 static void lcl_registerExtensions( 186 xmlXPathContextPtr ctx, 187 const extensions_t& extensions) 188 { 189 extensions_t::const_iterator i = extensions.begin(); 190 while (i != extensions.end()) 191 { 192 Libxml2ExtensionHandle aHandle = (*i)->getLibxml2ExtensionHandle(); 193 if ( aHandle.functionLookupFunction != 0 ) 194 { 195 xmlXPathRegisterFuncLookup(ctx, 196 reinterpret_cast<xmlXPathFuncLookupFunc>( 197 sal::static_int_cast<sal_IntPtr>(aHandle.functionLookupFunction)), 198 reinterpret_cast<void*>( 199 sal::static_int_cast<sal_IntPtr>(aHandle.functionData))); 200 } 201 if ( aHandle.variableLookupFunction != 0 ) 202 { 203 xmlXPathRegisterVariableLookup(ctx, 204 reinterpret_cast<xmlXPathVariableLookupFunc>( 205 sal::static_int_cast<sal_IntPtr>(aHandle.variableLookupFunction)), 206 reinterpret_cast<void*>( 207 sal::static_int_cast<sal_IntPtr>(aHandle.variableData))); 208 } 209 i++; 210 } 211 } 212 213 /** 214 * Use an XPath string to select a nodelist. 215 */ selectNodeList(const Reference<XNode> & contextNode,const OUString & expr)216 Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeList( 217 const Reference< XNode >& contextNode, 218 const OUString& expr) 219 { 220 Reference< XXPathObject > xobj = eval(contextNode, expr); 221 return xobj->getNodeList(); 222 } 223 224 /** 225 * same as selectNodeList but registers all name space decelerations found on namespaceNode 226 */ selectNodeListNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)227 Reference< XNodeList > SAL_CALL CXPathAPI::selectNodeListNS( 228 const Reference< XNode >& contextNode, 229 const OUString& expr, 230 const Reference< XNode >& namespaceNode) 231 { 232 lcl_collectRegisterNamespaces(*this, namespaceNode); 233 return selectNodeList(contextNode, expr); 234 } 235 236 /** 237 * Same as selectNodeList but returns the first node (if any) 238 */ selectSingleNode(const Reference<XNode> & contextNode,const OUString & expr)239 Reference< XNode > SAL_CALL CXPathAPI::selectSingleNode( 240 const Reference< XNode >& contextNode, 241 const OUString& expr) 242 { 243 Reference< XNodeList > aList = selectNodeList(contextNode, expr); 244 Reference< XNode > aNode = aList->item(0); 245 return aNode; 246 } 247 248 /** 249 * Same as selectSingleNode but registers all namespaces declared on 250 * namespaceNode 251 */ selectSingleNodeNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)252 Reference< XNode > SAL_CALL CXPathAPI::selectSingleNodeNS( 253 const Reference< XNode >& contextNode, 254 const OUString& expr, 255 const Reference< XNode >& namespaceNode ) 256 { 257 lcl_collectRegisterNamespaces(*this, namespaceNode); 258 return selectSingleNode(contextNode, expr); 259 } 260 261 #if LIBXML_VERSION >= 21200 make_error_message(const xmlError * pError)262 static OUString make_error_message(const xmlError *pError) 263 #else 264 static OUString make_error_message(xmlError *pError) 265 #endif 266 { 267 ::rtl::OUStringBuffer buf; 268 if (pError->message) { 269 buf.appendAscii(pError->message); 270 } 271 int line = pError->line; 272 if (line) { 273 buf.appendAscii("Line: "); 274 buf.append(static_cast<sal_Int32>(line)); 275 buf.appendAscii("\n"); 276 } 277 int column = pError->int2; 278 if (column) { 279 buf.appendAscii("Column: "); 280 buf.append(static_cast<sal_Int32>(column)); 281 buf.appendAscii("\n"); 282 } 283 OUString msg = buf.makeStringAndClear(); 284 return msg; 285 } 286 287 extern "C" { 288 generic_error_func(void * userData,const char * format,...)289 static void generic_error_func(void *userData, const char *format, ...) 290 { 291 (void) userData; 292 char str[1000]; 293 va_list args; 294 295 va_start(args, format); 296 #ifdef _WIN32 297 #define vsnprintf _vsnprintf 298 #endif 299 vsnprintf(str, sizeof(str), format, args); 300 va_end(args); 301 302 ::rtl::OUStringBuffer buf( 303 OUString::createFromAscii("libxml2 error:\n")); 304 buf.appendAscii(str); 305 OString msg = OUStringToOString(buf.makeStringAndClear(), 306 RTL_TEXTENCODING_ASCII_US); 307 OSL_ENSURE(sal_False, msg.getStr()); 308 } 309 310 #if LIBXML_VERSION >= 21200 structured_error_func(void * userData,const xmlError * error)311 static void structured_error_func(void * userData, const xmlError *error) 312 #else 313 static void structured_error_func(void * userData, xmlError *error) 314 #endif 315 { 316 (void) userData; 317 ::rtl::OUStringBuffer buf( 318 OUString::createFromAscii("libxml2 error:\n")); 319 if (error) { 320 buf.append(make_error_message(error)); 321 } else { 322 buf.append(OUString::createFromAscii("no error argument!")); 323 } 324 OString msg = OUStringToOString(buf.makeStringAndClear(), 325 RTL_TEXTENCODING_ASCII_US); 326 OSL_ENSURE(sal_False, msg.getStr()); 327 } 328 329 } // extern "C" 330 331 /** 332 * evaluates an XPath string. relative XPath expressions are evaluated relative to 333 * the context Node 334 */ eval(Reference<XNode> const & xContextNode,const OUString & expr)335 Reference< XXPathObject > SAL_CALL CXPathAPI::eval( 336 Reference< XNode > const& xContextNode, 337 const OUString& expr) 338 { 339 if (!xContextNode.is()) { throw RuntimeException(); } 340 341 nsmap_t nsmap; 342 extensions_t extensions; 343 344 { 345 ::osl::MutexGuard const g(m_Mutex); 346 nsmap = m_nsmap; 347 extensions = m_extensions; 348 } 349 350 // get the node and document 351 ::rtl::Reference<DOM::CDocument> const pCDoc( 352 dynamic_cast<DOM::CDocument*>( DOM::CNode::GetImplementation( 353 xContextNode->getOwnerDocument()))); 354 if (!pCDoc.is()) { throw RuntimeException(); } 355 356 DOM::CNode *const pCNode = DOM::CNode::GetImplementation(xContextNode); 357 if (!pCNode) { throw RuntimeException(); } 358 359 ::osl::MutexGuard const g(pCDoc->GetMutex()); // lock the document! 360 361 xmlNodePtr const pNode = pCNode->GetNodePtr(); 362 if (!pNode) { throw RuntimeException(); } 363 xmlDocPtr pDoc = pNode->doc; 364 365 /* NB: workaround for #i87252#: 366 libxml < 2.6.17 considers it an error if the context 367 node is the empty document (i.e. its xpathCtx->doc has no 368 children). libxml 2.6.17 does not consider it an error. 369 Unfortunately, old libxml prints an error message to stderr, 370 which (afaik) cannot be turned off in this case, so we handle it. 371 */ 372 if (!pDoc->children) { 373 throw XPathException(); 374 } 375 376 /* Create xpath evaluation context */ 377 ::boost::shared_ptr<xmlXPathContext> const xpathCtx( 378 xmlXPathNewContext(pDoc), xmlXPathFreeContext); 379 if( !bool(xpathCtx)) { throw XPathException(); } 380 381 // set context node 382 xpathCtx->node = pNode; 383 // error handling 384 xpathCtx->error = structured_error_func; 385 xmlSetGenericErrorFunc(NULL, generic_error_func); 386 387 // register namespaces and extension 388 lcl_registerNamespaces(xpathCtx.get(), nsmap); 389 lcl_registerExtensions(xpathCtx.get(), extensions); 390 391 /* run the query */ 392 OString o1 = OUStringToOString(expr, RTL_TEXTENCODING_UTF8); 393 xmlChar *xStr = (xmlChar*)o1.getStr(); 394 ::boost::shared_ptr<xmlXPathObject> const xpathObj( 395 xmlXPathEval(xStr, xpathCtx.get()), xmlXPathFreeObject); 396 if (0 == xpathObj) { 397 // OSL_ENSURE(xpathCtx->lastError == NULL, xpathCtx->lastError->message); 398 throw XPathException(); 399 } 400 Reference<XXPathObject> const xObj( 401 new CXPathObject(pCDoc, pCDoc->GetMutex(), xpathObj)); 402 return xObj; 403 } 404 405 /** 406 * same as eval but registers all namespace declarations found on namespaceNode 407 */ evalNS(const Reference<XNode> & contextNode,const OUString & expr,const Reference<XNode> & namespaceNode)408 Reference< XXPathObject > SAL_CALL CXPathAPI::evalNS( 409 const Reference< XNode >& contextNode, 410 const OUString& expr, 411 const Reference< XNode >& namespaceNode) 412 { 413 lcl_collectRegisterNamespaces(*this, namespaceNode); 414 return eval(contextNode, expr); 415 } 416 417 /** 418 * uses the service manager to create an instance of the service denoted by aName. 419 * If the returned object implements the XXPathExtension interface, it is added to the list 420 * of extensions that are used when evaluating XPath strings with this XPathAPI instance 421 */ registerExtension(const OUString & aName)422 void SAL_CALL CXPathAPI::registerExtension( 423 const OUString& aName) 424 { 425 ::osl::MutexGuard const g(m_Mutex); 426 427 // get extension from service manager 428 Reference< XXPathExtension > const xExtension( 429 m_aFactory->createInstance(aName), UNO_QUERY_THROW); 430 m_extensions.push_back(xExtension); 431 } 432 433 /** 434 * registers the given extension instance to be used by XPath evaluations performed through this 435 * XPathAPI instance 436 */ registerExtensionInstance(Reference<XXPathExtension> const & xExtension)437 void SAL_CALL CXPathAPI::registerExtensionInstance( 438 Reference< XXPathExtension> const& xExtension) 439 { 440 if (!xExtension.is()) { 441 throw RuntimeException(); 442 } 443 ::osl::MutexGuard const g(m_Mutex); 444 m_extensions.push_back( xExtension ); 445 } 446 } 447