Значение HtmlAgilityPack NextSibling.InnerText пусто.

Я очищаю некоторые данные с помощью HtmlAgilityPack.

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>

Теперь у меня проблема в том, что не всегда есть определенное количество полей, поэтому я не могу надежно получить доступ к каждому из них, например:

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

поскольку dd[1] может быть именем на одной странице и телефоном на другой, где пользователь не смог заполнить имя, поэтому поле скрыто.

поэтому я захватываю все узлы DT и DD следующим образом:

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

Теперь я проверяю каждый узел, чтобы увидеть, соответствует ли он нужному полю, и беру значение NextSibling следующим образом:

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

Это отлично работает для телефона, но по какой-то причине, когда появляется узел «Электронная почта:», оба NextSibling.InnerHTML и NextSibling.InnerText пусты, хотя у следующего брата определенно есть данные. Если я на самом деле перейду к этому node в details и посмотрю на него, InnerHTML — это вся отформатированная ссылка, а InnerText — это адрес электронной почты.

NextSibling.InnerText не работает, потому что тег A делает его дочерним или что-то в этом роде? Я посмотрел в отладчике и просто не могу найти нужную мне информацию под NextSibling.

Я уверен, что ответ смехотворно прост, я просто не могу понять. Кто-нибудь избавил меня от моих страданий?


person Guerrilla    schedule 27.08.2014    source источник
comment
Несколько отдельный вопрос, но почему вы выбираете элементы dd, если вы на самом деле не планируете использовать выбранные dd при переборе details?   -  person JLRishe    schedule 27.08.2014
comment
Так что я могу выбрать nextSibling. Если я не выберу DD, то они не станут следующим братом или сестрой.   -  person Guerrilla    schedule 27.08.2014
comment
Содержимое details не имеет никакого отношения к родственным узлам узла, и это именно то, что вы здесь видите.   -  person JLRishe    schedule 27.08.2014


Ответы (1)


Причина, по которой это происходит, заключается в том, что если node является элементом dt, который отделен от соответствующего ему элемента dd некоторым пробелом, то node.NextSibling является текстовым узлом, полностью состоящим из пробелов (пробел между </dt> и <dd>). Если вы посмотрите на это в отладчике, вы увидите, что NodeType node.NextSibling это HtmlNodeType.Text, а не HtmlNodeType.Element.

Я предлагаю создать удобный метод для получения текста узла dt, соответствующего dd:

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

Затем вы можете использовать его следующим образом:

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

Вот разбивка несколько хитрого XPath, используемого в моем методе выше:

(a) following-sibling::*

^ Выберите все элементы, которые имеют того же родителя, что и текущий узел, и находятся после него.

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

^ Выберите первый узел в наборе (а) (если есть)

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

^ Выберите все узлы в наборе (b), которые являются элементами с именем "dd"

SelectSingleNode() выбирает первый узел в наборе (c), который всегда должен быть либо 1, либо 0 узлами.

Скорее всего, вы могли бы обойтись только following-sibling::dd или following-sibling::*, но указанный выше путь содержит меры предосторожности. Например, если по какой-то причине у вас был следующий XML, а вашим текущим узлом был элемент Tel::

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

following-sibling::dd даст вам результат "Фальшивая улица, 50", а following-sibling::* даст вам результат "Адрес:". Вместо этого following-sibling::*[1][self::dd] в этом случае выберет пустой набор узлов, поэтому в результате метод правильно выдаст пустую строку.

person JLRishe    schedule 27.08.2014
comment
Гах, опереди меня на 5 секунд :) (И с кодом!) - person paul; 27.08.2014
comment
Спасибо, работает отлично. Что меня смутило, так это то, что я открыл details в отладчике и увидел, что электронная почта: была в [0], а затем адрес электронной почты был в [1], поэтому я подумал, что NextSibling получит эту следующую запись. Я новичок в xpath и не совсем понимаю, как работает ваш xpath, я пытался понять его по ссылке, но не полностью понял. Я думаю, мне нужно получить книгу об этом. - person Guerrilla; 28.08.2014
comment
следующие братья и сестры = брать все html после этого узла * = брать любой html. [1] = ?? [self::dd] = выбирает текущий узел и выбирает dd? код все еще работает, если я удалю этот бит - person Guerrilla; 28.08.2014
comment
@Guerrilla Добавлена ​​деконструкция выше. - person JLRishe; 28.08.2014
comment
@JLRishe спасибо! Я очень ценю, что вы нашли время, чтобы объяснить это мне. - person Guerrilla; 28.08.2014