2016-06-23 57 views
1

I имеет следующую структуру, где дочерние узлы находятся в случайном порядке:Как выбрать текст() сразу после элемента условно в 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. Если цвет стиля «blue», затем добавьте значение узла в строку
  2. Если класс является «основным», то добавьте значение узла в строку
  3. Весь текст(), не заключенный в теги, будет добавлен в строку, но в порядке обхода всех ребенок узлы.

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

51 Gill $500 James 

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

$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; 

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

К примеру, в этом блоке:

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

Джеймс находится между пролетами и Div узлов. Поэтому мы добавляем его в строку.

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

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

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

это не должно быть добавлен.

Пожалуйста, посмотрите мое выражение $ в PHP-коде, где я пытаюсь захватить этот процесс, но он не работает.

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

ответ

0

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

<?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 Джеймс

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

+0

Благодаря тонну @KeithHall. Он отлично работает! Я не знал о funciton node() в next-sibling :: node() [1]. Еще раз спасибо за быстрый ответ! –

+0

Также @KeithHall, очень ценю, что вы выписываете код, тестируете его и даете четкие объяснения для каждого шага. –

0

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 
    "