2016-06-23 1 views
1

J'ai la structure suivante où les nœuds enfants sont dans un ordre aléatoire:Comment sélectionner le texte() suivant immédiatement un élément sous 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> 

Je suis en train de concaténer des chaînes de caractères (en laissant un espace entre les deux) en fonction des conditions:

  1. Si la couleur de style est "bleu" puis ajoute la valeur du noeud à la chaîne
  2. Si la classe est "main", alors ajouter la valeur du noeud à la chaîne
  3. Tout le texte() non inclus dans les balises sera ajouté à la chaîne mais dans l'ordre de traversée de tous les enfant nœuds.

L'exemple de sortie de la structure ci-dessus devrait être:

51 Gill $500 James 

I ont écrit ce qui suit dans PHP pour traverser les éléments. On peut sauter la lecture de cette partie si elle est verbeuse. L'accent est mis sur l'expression de $ pour sélectionner des valeurs de noeud de texte() si elle se produit immédiatement après un élément:

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

L'objectif principal, comme mentionné précédemment, est de saisir le texte() valeurs de nœud si se produit immédiatement après un élément. Je veux écrire une expression XPATH qui fait ce qui suit: 1. Sélectionnez le premier nœud text() après un élément 2. Vérifiez si ce nœud text() se trouve entre le nœud autonome (nœud actuel) et le nœud immédiatement suivant .

Par exemple, dans ce bloc:

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

James est entre la portée et les noeuds div. Nous l'avons donc ajouté à la chaîne.

Mais dans ce bloc:

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

déclaration James serait encore sélectionné par following-sibling [1] par rapport au premier élément span (avec la couleur: rouge)

Cela ne devrait pas être ajouté.

Veuillez voir mon expression $ dans le code PHP où j'essaye de capturer ce processus mais cela ne fonctionne pas.

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

Répondre

0

Vous pouvez y parvenir avec les éléments suivants:

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

La requête XPath:

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

dit:

  • . à partir du noeud contextuel
  • following-sibling::node()[1] prendre le premier suivant le noeud frère (que ce soit un nœud de texte ou d'un élément (ou même un commentaire))
  • self::text()[normalize-space()] prendre le nœud « courant » si elle est un nœud de texte et ne consiste pas seulement des espaces

sortie est:

51 500 $ Gill James

Cela traitera également le scénario dans lequel vous pourriez avoir un nœud de texte après la las t élément enfant du parent <span id="outer">.

+0

Merci une tonne @KeithHall. Cela fonctionne parfaitement! Je ne connaissais pas la fonction node() dans following-sibling :: node() [1]. Merci encore pour la réponse rapide! –

+0

Aussi @KeithHall, vraiment apprécier votre écriture du code, le tester et donner des explications claires pour chaque étape. –

0

Xpath prend en charge les axes. En les utilisant, vous pouvez spécifier les noeuds qui seront initialement trouvés. L'axe par défaut est child et le @ est l'abréviation de attribute. Les axes dont vous avez besoin dans ce cas sont following-sibling et self.

Si vous utilisez span[@class = "main"] pour spécifier le noeud marqueur, vous pouvez l'étendre à span[@class = "main"]/following-sibling::node()[1] et récupérer le noeud suivant. Pour vous assurer qu'il s'agit d'un nœud de texte avec span[@class = "main"]/following-sibling::node()[1]/self::text()

Au moment où vous réitérez tous les nœuds, mais à l'exception des attributs style, vous pouvez faire correspondre les valeurs directement dans Xpath. Et pour les conditions de style que vous pouvez utiliser un rappel en 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) 
); 
} 

Sortie:

string(2) "51" 
string(4) "Gill" 
string(10) "$500 
    " 
string(11) "James 
    "