libxml2: 相对于子节点的 xpath

libxml2: xpath relative to sub node

给定 xml 文件

<a>
   <b>
      <d>v</d>
   </b>
   <c>
      <d>v</d>
   </c>
</a>

和 xpath "//d/text()"

我只想将 xpath 应用于 c 而不是整个文档。

这必须与 libxml2 2.7.6

一起使用

这样做是行不通的;

xmlNodePtr node = <node pointer to c>
xmlXPathContextPtr xpathCtx = xmlXPathNewContext( node->doc );
xpathCtx->node = node;

xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression ( "//d/text()", xpathCtx);

返回的 xpathObj 包含对 /a/b/d 和 /a/c/d 的引用。预计只有 /a/c/d。

如果要查找相对于另一个节点的后代,请使用路径 .//d.//d/text()。以 //d 开头的路径搜索根节点(也称为文档节点)的所有 d 个后代元素。

解决方法:

xmlNodePtr node = <node pointer to c>

xmlXPathContextPtr xpathCtx = xmlXPathNewContext( node->doc );

//Update the document to set node as root
xmlNodePtr myParent = node->parent;
xmlNodePtr originalRootElement = xmlDocGetRootElement( node->doc );
xmlDocSetRootElement( node->doc, node );

xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression ( "//d/text()", xpathCtx);

//xpathObj contains only /a/c/d, as expected

//restore the xml document
xmlDocSetRootElement( originalRootElement->doc, originalRootElement );
xmlAddChild( myParent, node );

使用 valdrind 测试。

上面的例子给出了这个想法。下面是完整的实现(感谢 Julo 的评论):

//Update Doc to support xpath on logical root node and restore the document structure at scope exit
class XmlDoc_UpdateDocAndRestoreAtScopeExit {
public:
    XmlDoc_UpdateDocAndRestoreAtScopeExit(  _xmlDoc* doc, _xmlNode* logicalRootNode) :
            _doc                ( doc              ),
            _logicalRootNode    ( logicalRootNode  ),
            _originalRootElement( 0 ),
            _refParent          ( 0 ),
            _refPrevSibling     ( 0 ),
            _refNextSibling     ( 0 ),
            _toRestore(false)
{
    _originalRootElement = xmlDocGetRootElement( doc );
    _refParent = _logicalRootNode->parent;
    _refPrevSibling = _logicalRootNode->prev;
    _refNextSibling = _logicalRootNode->next;

    if ( _logicalRootNode != _originalRootElement ) {
        xmlDocSetRootElement( _doc, _logicalRootNode );
        _toRestore = true;
    }

}

~XmlDoc_UpdateDocAndRestoreAtScopeExit() {

    if ( _toRestore ) { 

        //Restore the root node
        xmlDocSetRootElement( _doc, _originalRootElement );

        //Restore the node at its original place
        if ( 0 != _refPrevSibling ) {
            xmlAddNextSibling( _refPrevSibling, _logicalRootNode );

        } else if ( 0 != _refNextSibling )  {
            xmlAddPrevSibling( _refNextSibling, _logicalRootNode );

        } else {
            xmlAddChild( _refParent, _logicalRootNode );
        }
    }
}
private:
    XmlDoc_UpdateDocAndRestoreAtScopeExit() ; // not implemented
    XmlDoc_UpdateDocAndRestoreAtScopeExit(const XmlDoc_UpdateDocAndRestoreAtScopeExit &) ; // not implemented
    XmlDoc_UpdateDocAndRestoreAtScopeExit & operator= (const XmlDoc_UpdateDocAndRestoreAtScopeExit &) ; // not implemented
private:
    _xmlDoc* _doc;
    _xmlNode* _logicalRootNode;
    _xmlNode* _originalRootElement;
    _xmlNode* _refParent;
    _xmlNode* _refPrevSibling;
    _xmlNode* _refNextSibling;
    bool _toRestore;
};

//Somewhere in the code...
xmlNodePtr node = <node pointer to c>

xmlXPathContextPtr xpathCtx = xmlXPathNewContext( node->doc );

//Here set the _rootNodePtr as the root of the document to use xpath on your "logical" root
//At scope exit the document's structure will be restored.
XmlDoc_UpdateDocAndRestoreAtScopeExit xmlDoc_UpdateDocAndRestoreAtScopeExit( node->doc, node );

xmlXPathObjectPtr xpathObj = xmlXPathEvalExpression ( "//d/text()", xpathCtx);
//xpathObj contains only /a/c/d, as expected