Как выбрать все узлы DOMDocument с одним выражением DOMXpath? - PullRequest
4 голосов
/ 22 января 2012

Что такое выражение xpath для выбора всех узлов документа?

С учетом этого примера XML:

<div class="header"/>

I содержит три узла: <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 ]

3 голосов
/ 22 января 2012

Ваш пример на самом деле содержит только два узла: элемент (div) и атрибут (class="header"). Таким образом, «заголовок» - это значение атрибута, а не отдельный узел.

Текстовые узлы существуют, но они используются для текста между элементами. Например, в <title>Alice in wonderland</title> есть два узла: элемент (title) и текстовый узел (Alice in wonderland).

Следовательно, лучшее, что вы можете сделать в этом случае, это //*|//@*.

РЕДАКТИРОВАТЬ, после вашего обновления в вопросе.

Наличие текстового узла связано с реализацией, специфичной для php, и не является частью стандарта W3C . XPath рассматривает только 2 узла независимо от реализации.

Сказав это, вы можете использовать некоторые функции XPath , чтобы получить то, что вы хотите. Функция name() возвращает имя узла, а функция string() возвращает строковое значение. Может быть, вы могли бы использовать их для получения строк в результате (вместо узлов).

3 голосов
/ 23 января 2012

Используйте

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

это выбирает любой узел (типа узла документа /, узел элемента, текстовый узел, узел инструкции обработки и узел комментария) и любой узел атрибута и любой узел пространства имен - то есть все узлы, потому что нет других типов узлов.

Способ доступа к полученному списку XmlNodeList, содержащему выбранные узлы, зависит от API конкретного используемого вами механизма XPath - прочитайте и используйте документацию.

Пример на основе 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>

когда это преобразование XSLT применяется к любому XML-документу, оно выбирает все узлы, используя приведенное выше выражение 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
1 голос
/ 22 января 2012

Вы пробовали что-то вроде //*|//@*|//text()?

0 голосов
/ 24 сентября 2017
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;
}
...