HtmlAgilityPack NextSibling.InnerText est vide

c# html-agility-pack siblings xpath

Question

Je suis en train de gratter des données en utilisant HtmlAgilityPack.

Le HTML ressemble à ceci:

<div id="id-here">
  <dl>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
  </dl>
</div>

Maintenant, le problème que j’ai, c’est qu’il n’ya pas toujours un nombre défini de champs, je ne peux donc pas accéder de manière fiable à chacun d’eux comme:

//*[@id="id-here"]/dl[1]/dd[1]

comme dd [1] peut être un nom sur une page et un téléphone sur une autre où l'utilisateur n'a pas renseigné le nom, de sorte que le champ est masqué.

donc je prends tous les nœuds DT et DD comme suit:

//*[@id="id-here"]/dl[1]/dt | //*[@id="id-here"]/dl[1]/dd

Maintenant, je vérifie chaque nœud pour voir s'il correspond au champ que je veux et prends la valeur NextSibling comme suit:

    foreach (HtmlNode node in details)
    {
        if (node.InnerText.Contains("Tel:")) telephone = node.NextSibling.InnerText;
        if (node.InnerText.Contains("Email:")) email = node.NextSibling.InnerText;
    }

Cela fonctionne très bien pour le téléphone, mais pour une raison quelconque, lorsque le nœud "Email:" apparaît, NextSibling.InnerHTML et NextSibling.InnerText sont vides bien que le prochain frère ait définitivement les données. Si je vais dans les details à ce node et que je le InnerHTML est l’ensemble du lien formaté et InnerText est l’adresse électronique.

NextSibling.InnerText ne fonctionne-t- NextSibling.InnerText pas parce que la balise A en fait un enfant ou quelque chose? J'ai jeté un coup d'œil dans le débogueur et je ne trouve pas l'information dont j'ai besoin sous NextSibling .

Je suis sûr que la réponse est ridiculement simple, je ne peux pas le comprendre. Quelqu'un m'a mis hors de ma misère?

Réponse acceptée

La raison en est que si node est un élément dt qui est séparé de son élément dd correspondant par des espaces, alors node.NextSibling est un nœud de texte node.NextSibling espaces (l'espace entre les node.NextSibling </dt> et <dd> ). Si vous le regardez dans le débogueur, vous verrez que le node.NextSibling de NodeType est HtmlNodeType.Text et non HtmlNodeType.Element .

Je suggère de créer une méthode pratique pour obtenir le texte du dd correspondant d'un nœud dt :

internal static string GetMatchingDdValue(HtmlNode dtNode)
{
    var found = dtNode.SelectSingleNode("following-sibling::*[1][self::dd]");
    return found == null ? "" : found.InnerText;
}

Ensuite, vous pouvez l'utiliser comme ceci:

if (node.InnerText.Contains("Tel:")) { telephone = GetMatchingDdValue(node); }

Voici un aperçu du XPath un peu délicat utilisé dans ma méthode ci-dessus:

(a) following-sibling::*

^ Sélectionnez tous les éléments qui partagent le même parent que le nœud actuel et apparaissent après celui-ci.

(b) following-sibling::*[1]

^ Sélectionnez le premier noeud de l'ensemble (a) (s'il y en a)

(c) following-sibling::*[1][self::dd] 

^ Sélectionnez tous les nœuds de l'ensemble (b) qui sont des éléments portant le nom "dd"

SelectSingleNode() sélectionne le premier nœud de l'ensemble (c), qui doit toujours être 1 ou 0 nœud.

Vous pourriez très probablement vous en tirer avec juste following-sibling::dd ou following-sibling::dd following-sibling::* , mais le chemin ci-dessus contient des sauvegardes. Par exemple, si pour une raison quelconque, vous aviez le code XML suivant et que votre nœud actuel était l'élément Tel: :

<dl>
  <dt>Tel:</dt>
  <dt>Address:</dt>
  <dd>50 Fake St.</dd>
</dl>

following-sibling::dd vous donnerait le résultat "50 Fake St.", tandis que vous following-sibling::* vous donnerait le résultat "Address:". Follow following-sibling::*[1][self::dd] sélectionnerait un ensemble de nœuds vide dans ce cas, de sorte que la méthode produirait correctement une chaîne vide comme résultat.


Réponse populaire

var html = @"
<div id='id-here'>
  <dl>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
    <dt> Field Name </dt>
    <dd> Value for above field name </dd>
  </dl>
</div>";
html = new Regex(">\r\n\\s*<").Replace(html,"><");
var doc = new HtmlAgilityPack.HtmlDocument();
doc.LoadHtml(html);
Console.Write(doc.DocumentNode.SelectNodes("//dt")[0].NextSibling.OuterHtml);

<dd> Value for above field name </dd>


Related

Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow