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