El valor HtmlAgilityPack NextSibling.InnerText está en blanco

c# html-agility-pack siblings xpath

Pregunta

Estoy raspando algunos datos usando HtmlAgilityPack.

El HTML se ve así:

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

Ahora el problema que tengo es que no siempre hay un número determinado de campos, por lo que no puedo acceder a cada uno de ellos de manera confiable, como:

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

como dd [1] puede ser un nombre en una página y un teléfono en otra donde el usuario no pudo completar un nombre para que el campo esté oculto.

Así que tomo todos los nodos DT y DD así:

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

Ahora reviso cada nodo para ver si coincide con el campo que quiero y tomo el valor de NextSibling así:

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

Esto funciona bien para el teléfono, pero por alguna razón, cuando aparece el nodo "Correo electrónico:", tanto NextSibling.InnerHTML NextSibling.InnerText están en blanco, aunque el próximo hermano definitivamente tiene los datos. Si realmente voy a ese node en details y lo miro, el InnerHTML es todo el enlace formateado y el InnerText es la dirección de correo electrónico.

¿ NextSibling.InnerText está funcionando NextSibling.InnerText porque la etiqueta A hace que sea un niño o algo así? He NextSibling un vistazo al depurador y no puedo encontrar la información que necesito en NextSibling .

Estoy seguro de que la respuesta es ridículamente simple, simplemente no puedo entenderlo. ¿Alguien me sacó de mi miseria?

Respuesta aceptada

La razón por la que ocurre esto es que si node es un elemento dt que está separado de su elemento dd correspondiente por algún espacio en blanco, entonces node.NextSibling es un nodo de texto de espacio en blanco (el espacio entre </dt> y <dd> ). Si lo observa en el depurador, verá que node.NextSibling 's NodeType es HtmlNodeType.Text y no HtmlNodeType.Element .

Sugiero crear un método conveniente para obtener el texto del dd correspondiente del nodo dt :

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

Entonces puedes usarlo así:

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

Aquí hay un desglose del XPath algo complicado utilizado en mi método anterior:

(a) following-sibling::*

^ Seleccione todos los elementos que compartan el mismo padre que el nodo actual y se produzcan después de él.

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

^ Seleccione el primer nodo en el conjunto (a) (si hay alguno)

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

^ Seleccione todos los nodos en el conjunto (b) que son elementos con el nombre "dd"

SelectSingleNode() selecciona el primer nodo en el conjunto (c), que siempre debe ser 1 o 0 nodos.

Lo más probable es que puedas arreglártelas solo con following-sibling::dd o following-sibling::* , pero la ruta anterior contiene salvaguardas. Por ejemplo, si por alguna razón, tenía el siguiente XML y su nodo actual era el elemento Tel: :

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

following-sibling::dd le daría el resultado "50 Fake St.", mientras que following-sibling::* le daría el resultado "Dirección:". En su lugar, following-sibling::*[1][self::dd] seleccionaría un conjunto de nodos vacío en este caso, por lo que el método produciría correctamente una cadena vacía como resultado.


Respuesta popular

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>


Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué