HtmlAgilityPack NextSibling.Il valore di InnerText è vuoto

c# html-agility-pack siblings xpath

Domanda

Sto raschiando alcuni dati usando HtmlAgilityPack.

L'HTML ha questo aspetto:

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

Ora il problema che ho è che non c'è sempre un numero fisso di campi, quindi non posso accedere in modo affidabile a ciascuno di essi come:

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

come dd [1] può essere un nome su una pagina e un telefono su un altro in cui l'utente non è riuscito a compilare un nome in modo che il campo sia nascosto.

quindi prendo tutti i nodi DT e DD in questo modo:

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

Ora controllo ogni nodo per vedere se corrisponde al campo che desidero e prendo il valore NextSibling in questo modo:

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

Funziona bene per il telefono, ma per qualche motivo quando viene visualizzato il nodo "Email:", sia NextSibling.InnerHTML NextSibling.InnerText sono vuoti sebbene il fratello successivo abbia sicuramente i dati. Se effettivamente vado a quel node nei details e lo guardo, InnerHTML è l'intero collegamento formattato e InnerText è l'indirizzo email.

NextSibling.InnerText non funziona perché il tag A lo sta rendendo un bambino o qualcosa del genere? Ho dato un'occhiata al debugger e non riesco a trovare le informazioni di cui ho bisogno in NextSibling .

Sono sicuro che la risposta è ridicolmente semplice, non riesco a capirlo. Qualcuno mi ha messo fuori dalla mia miseria?

Risposta accettata

La ragione per cui questo sta accadendo è che se il node è un elemento dt che è separato dal relativo elemento dd da qualche spazio, allora node.NextSibling è un nodo di testo interamente node.NextSibling (lo spazio tra </dt> e <dd> ). Se lo guardi nel debugger, vedrai che node.NextSibling 's NodeType è HtmlNodeType.Text e non HtmlNodeType.Element .

Suggerisco di creare un metodo comodo per ottenere il testo del dd corrispondente al nodo dt :

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

Quindi puoi usarlo in questo modo:

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

Ecco una ripartizione del XPath un po 'complicato usato nel mio metodo sopra:

(a) following-sibling::*

^ Seleziona tutti gli elementi che condividono lo stesso genitore del nodo corrente e si verificano dopo di esso.

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

^ Seleziona il primo nodo nel set (a) (se ce ne sono)

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

^ Seleziona tutti i nodi nel set (b) che sono elementi con il nome "dd"

SelectSingleNode() seleziona il primo nodo in set (c), che dovrebbe essere sempre 1 o 0 nodi.

Probabilmente si potrebbe cavarsela solo con following-sibling::dd o following-sibling::* , ma il percorso sopra contiene salvaguardie. Ad esempio, se per qualche motivo avevi il seguente codice XML e il tuo nodo corrente era Tel: element:

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

following-sibling::dd ti darebbe il risultato "50 Fake St.", mentre following-sibling::* ti darebbe il risultato "Indirizzo:". Invece, follow following-sibling::*[1][self::dd] selezionerebbe un nodeset vuoto in questo caso, quindi il metodo produrrebbe correttamente una stringa vuota come risultato.


Risposta popolare

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>


Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché