如何使用单个DOMXpath表达式选择DOMDocument的所有节点?

时间:2012-01-22 09:01:29

标签: php xpath domdocument

选择文档的所有节点的xpath表达式是什么?

给出这个例子XML:

<div class="header"/>

我包含三个节点:<div>(元素),class=(属性)和"header"(文字)。

$doc = new DOMDocument;
$doc->loadXml('<div class="header"/>');
$xpath = new DOMXPath($doc);

我尝试了//node()

$xpath->query('//node()');

仅返回所有元素节点(我假设因为//)。有没有办法在属性值中添加属性和文本节点等其他节点?


附加示例:

我可以使用DOMDocument API获取每个节点,例如获取属性值的文本节点:

$doc = new DOMDocument;
$doc->loadXml('<div class="header"/>');
$class = $doc->documentElement->getAttributeNode('class');
echo $class->childNodes->item(0)->nodeName;

给出了:

#text

如何使用一个xpath表达式获取所有节点的超集,尤其是包含该示例性class属性节点子文本节点?

4 个答案:

答案 0 :(得分:3)

您的示例实际上只包含两个节点:元素(div)和属性(class="header")。因此,“header”是属性的值,而不是单独的节点。

文本节点确实存在,但它们用于元素之间的文本。例如,在<title>Alice in wonderland</title>中,有两个节点:元素(title)和文本节点(Alice in wonderland)。

因此,在这种情况下,您可以做的最好的事情是//*|//@*

编辑,在您更新问题之后。

文本节点的存在是由于特定于php的实现,它不是W3C standard的一部分。无论实现如何,XPath都只考虑2个节点。

话虽如此,你可以使用一些XPath functions来获得你想要的东西。函数name()返回节点的名称,函数string()返回字符串值。也许你可以使用这些来获取字符串(而不是节点)。

答案 1 :(得分:3)

使用

//node() | //@* | //namespace::*

这将选择任何节点(类型为文档节点/,元素节点,文本节点,处理指令节点和注释节点)以及任何属性节点和任何命名空间节点 - 即 all 节点,因为没有其他类型的节点。

如何访问包含所选节点的获取的XmlNodeList取决于您使用的特定XPath引擎的API - 阅读并使用您的文档。

基于XSLT的示例

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/">

  <xsl:for-each select=
   "//node() | //@* | //namespace::*">

   Type: <xsl:text/>

   <xsl:choose>
    <xsl:when test="not(..)">
     <xsl:text>document node </xsl:text>
    </xsl:when>
    <xsl:when test="self::*">
     <xsl:text>element </xsl:text>
    </xsl:when>
    <xsl:when test="self::text()">
     <xsl:text>text-node </xsl:text>
    </xsl:when>
    <xsl:when test="self::comment()">
     <xsl:text>comment-node </xsl:text>
    </xsl:when>
    <xsl:when test="self::processing-instruction()">
     <xsl:text>PI-node </xsl:text>
    </xsl:when>
    <xsl:when test="count(.|../@*) = count(../@*)">
     <xsl:text>attribute-node </xsl:text>
    </xsl:when>
    <xsl:when test=
    "count(.|../namespace::*) = count(../namespace::*)">
     <xsl:text>namespace-node </xsl:text>
    </xsl:when>
   </xsl:choose>

   <xsl:text>Name: "</xsl:text>
   <xsl:value-of select="name()"/>" <xsl:text/>

   <xsl:text>Value: </xsl:text>
   <xsl:value-of select="."/>

  </xsl:for-each>

 </xsl:template>
</xsl:stylesheet>

当对任何XML文档应用此XSLT转换时,它使用上述XPath表达式选择所有节点(转换故意排除任何仅限空格的文本节点)并输出(按文档顺序)类型,名称和所选节点的字符串值

例如,应用于此XML文档时

<networkOfBridges xmlns:x="x">
    <bridge id="1"  otherside="A" />
    <!-- A Comment -->
    <bridge id="2"  oneside="A"/>
    <?PI Processing Instruction ?>
    <bridge id="3"  oneside="A" otherside="A" />
</networkOfBridges>

结果是

   Type: element Name: "networkOfBridges" Value: 

   Type: namespace-node Name: "xml" Value: http://www.w3.org/XML/1998/namespace

   Type: namespace-node Name: "x" Value: x

   Type: element Name: "bridge" Value: 

   Type: namespace-node Name: "xml" Value: http://www.w3.org/XML/1998/namespace

   Type: namespace-node Name: "x" Value: x

   Type: attribute-node Name: "id" Value: 1

   Type: attribute-node Name: "otherside" Value: A

   Type: comment-node Name: "" Value:  A Comment 

   Type: element Name: "bridge" Value: 

   Type: namespace-node Name: "xml" Value: http://www.w3.org/XML/1998/namespace

   Type: namespace-node Name: "x" Value: x

   Type: attribute-node Name: "id" Value: 2

   Type: attribute-node Name: "oneside" Value: A

   Type: PI-node Name: "PI" Value: Processing Instruction 

   Type: element Name: "bridge" Value: 

   Type: namespace-node Name: "xml" Value: http://www.w3.org/XML/1998/namespace

   Type: namespace-node Name: "x" Value: x

   Type: attribute-node Name: "id" Value: 3

   Type: attribute-node Name: "oneside" Value: A

   Type: attribute-node Name: "otherside" Value: A

答案 2 :(得分:1)

您是否尝试过类似//*|//@*|//text()的内容?

答案 3 :(得分:-1)

foreach ($xpath->query('//*[count(*) = 0]') as $node) {
    $path = array();
    $val = $node->nodeValue;
    do {
        $path[] = $node->nodeName;
    }
    while ($node = $node->parentNode);
    $result[implode('/', array_reverse($path))] = $val;
}