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