У меня есть следующая структура, где дочерние узлы расположены в случайном порядке:
<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>
Я пытаюсь объединить строки вместе (оставляя пробел между ними) на основе условий:
Пример вывода для вышеуказанной структуры должен быть:
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>
Джеймс будет по-прежнему выбираться с помощью оператора follow-sibling [1] относительно первого элемента span (с цветом: красный)
Это НЕ должно быть добавлено.
Пожалуйста, посмотрите мое выражение $ в коде PHP, где я пытаюсь захватить этот процесс, но он не работает.
$expression = ".//following-sibling::text()[1][preceding-sibling::".$current_node_name." and following-sibling::".$next_node_name."[contains(text(),'".$next_node_value."')]]";
Вы можете достичь этого с помощью следующего:
<?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">
,
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
"