Как выделить текст () сразу после элемента условно в XPath?

У меня есть следующая структура, в которой дочерние узлы расположены в случайном порядке:

<span id="outer">
     <div style="color:blue">51</div>
     <span class="main">Gill</span>$500
     <span style="color:red">11</span>
     <span></span>James
     <div style="color:red">158</div>
     <div class="sub">Mary</div>
</span>

Я пытаюсь объединить строки вместе (оставляя пробел между ними) на основе условий:

  1. Если цвет стиля "синий", добавьте значение узла в строку
  2. Если класс "основной", добавьте значение узла в строку
  3. Весь текст (), не заключенный в теги, будет добавлен в строку, но в порядке обхода всех дочерних узлов.

Пример вывода для приведенной выше структуры должен быть:

51 Gill $500 James

Я написал следующее на PHP для обхода элементов. Эту часть можно пропустить, если она многословна. Основное внимание уделяется выражению $ для выбора значений узла text (), если оно появляется сразу после элемента:

$nodes = $xpath->query("//span[@id='outer']/*");
$str_out = "";
foreach($nodes as $node)
{
    if($node->hasAttribute('class')
    {
        if($node->getAttribute('class')=="main")
            $str_out .= $node->nodeValue . " ";
    }

    else if($node->hasAttribute('style')
    {
        $node_style = $node->getAttribute('style');
        preg_match('~color:(.*)~', $node_style, $temp);
        if( $temp[1] == "red" )
            $str_out .= $node->nodeValue . " ";
    }

    // Now evaluate if the IMMEDIATELY next sibling is text()

    $next_node = $xpath->query('.//following-sibling::*[1]', $node);        
    if($next_node->length)
    {
        $next_node = $next_node->item(0);
        $next_node_name = $next_node->nodeName;         
        $next_node_value =  $next_node->nodeValue;
        $current_node_name = $node->nodeName;

        $expression = ".//following-sibling::text()[1][preceding-sibling::".$current_node_name." and following-sibling::".$next_node_name."[contains(text(),'".$next_node_value."')]]";

        $text_node = $xpath->query($expression, $node);
        if($text_node->length)              
        {           
            $str_out .= $text_node->item(0)->nodeValue . " ";               
        }
    }
}
echo $str_out;

Как упоминалось ранее, основное внимание уделяется захвату значений узла text (), если это происходит сразу после элемента. Я хочу написать выражение XPATH, которое выполняет следующие действия: 1. Выбирает первый узел text () после элемента 2. Проверяет, находится ли этот узел text () между собственным узлом (текущим узлом) и следующим за ним узлом.

Например в этом блоке:

<span></span>James
<div style="color:red">158</div>

Джеймс находится между узлами span и div. Итак, мы добавляем его в строку.

Но в этом блоке:

<span style="color:red">11</span>
<span></span>James
<div style="color:red">158</div>

Джеймс по-прежнему будет выбран оператором following-sibling [1] относительно первого элемента диапазона (с цветом: красный)

Этого НЕ следует добавлять.

См. Мое выражение $ в коде PHP, где я пытаюсь зафиксировать этот процесс, но он не работает.

$expression = ".//following-sibling::text()[1][preceding-sibling::".$current_node_name." and following-sibling::".$next_node_name."[contains(text(),'".$next_node_value."')]]";

person Adam Ranganathan    schedule 23.06.2016    source источник


Ответы (2)


Вы можете добиться этого с помощью следующего:

<?php
$xmldoc = new DOMDocument();
$xmldoc->loadXML(<<<XML
<span id="outer">
     <div style="color:blue">51</div>
     <span class="main">Gill</span>$500
     <span style="color:red">11</span>
     <span></span>James
     <div style="color:red">158</div>
     <div class="sub">Mary</div>
</span>
XML
);
$xpath = new Domxpath($xmldoc);

$nodes = $xpath->query("//span[@id='outer']/*");
$str_out = "";
foreach ($nodes as $node)
{
    if ($node->hasAttribute('class'))
    {
        if ($node->getAttribute('class') == "main")
            $str_out .= $node->nodeValue . " ";
    }

    else if ($node->hasAttribute('style'))
    {
        $node_style = $node->getAttribute('style');
        preg_match('~color:(.*)~', $node_style, $temp);
        if ($temp[1] == "blue")
            $str_out .= $node->nodeValue . " ";
    }

    // Now evaluate if the IMMEDIATELY next sibling is text()
    $next_node = $xpath->query('./following-sibling::node()[1]/self::text()[normalize-space()]', $node);
    if ($next_node->length)
    {
        $str_out .= trim($next_node->item(0)->nodeValue) . " ";
    }
}
echo $str_out;

Запрос XPath:

./following-sibling::node()[1]/self::text()[normalize-space()]

говорит:

  • . из контекстного узла
  • following-sibling::node()[1] взять первый следующий родственный узел (будь то текстовый узел или элемент (или даже комментарий))
  • self::text()[normalize-space()] взять "текущий" узел, если это текстовый узел и не состоит только из пробелов

Выход:

51 Гилл Джеймс, $ 500

Это также будет обрабатывать сценарий, в котором у вас может быть текстовый узел после последнего дочернего элемента родительского <span id="outer">.

person Keith Hall    schedule 23.06.2016

Xpath поддерживает оси. Используя их, вы можете указать, какие узлы будут совпадать изначально. Ось по умолчанию - child, а @ - сокращение от attribute. В этом случае вам понадобятся оси following-sibling и self.

Если вы используете span[@class = "main"] для указания узла маркера, вы можете расширить его до span[@class = "main"]/following-sibling::node()[1] и получить следующий узел. Чтобы убедиться, что это текстовый узел с span[@class = "main"]/following-sibling::node()[1]/self::text()

В настоящий момент вы повторяете все узлы, но, за исключением атрибутов style, вы можете сопоставить значения непосредственно в Xpath. А для условий стиля вы можете использовать обратный вызов в PHP:

$xml = <<<'XML'
<span id="outer">
     <div style="color:blue">51</div>
     <span class="main">Gill</span>$500
     <span style="color:red">11</span>
     <span></span>James
     <div style="color:red">158</div>
     <div class="sub">Mary</div>
</span>
XML;

function getStyleProperty($node, $name) { 
  if (is_array($node)) {
    $node = $node[0];
  }
  if ($node instanceof DOMElement) {
    $pattern = sprintf(
    '(\b%s:\s*([^;]*)\s*(;|$))', preg_quote($name)
    );
    if (preg_match($pattern, $node->getAttribute('style'), $matches)) {
      return $matches[1];
    }
  }
  return '';
}

$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
$xpath->registerNamespace('php', 'http://php.net/xpath');
$xpath->registerPHPFunctions(['getStyleProperty']);

foreach ($xpath->evaluate('//span[@id="outer"]')as $outer) {
  var_dump(
    $xpath->evaluate('string(div[php:function("getStyleProperty", ., "color") = "blue"])', $outer),
    $xpath->evaluate('string(span[@class = "main"])', $outer),
    $xpath->evaluate('string(span[@class = "main"]/following-sibling::text()[1])', $outer),
    $xpath->evaluate('string(span[not(@class or @style)]/following-sibling::node()[1]/self::text())', $outer)
  );
}

Вывод:

string(2) "51"
string(4) "Gill"
string(10) "$500
     "
string(11) "James
     "
person ThW    schedule 23.06.2016