Разбор тегов, которые не закрыты с веб-страницы с помощью HtmlAgilityPack

c# html-agility-pack linq

Вопрос

Я пытаюсь разобрать список станций с сайта NOAA (weather.noaa.gov). Если вы посмотрите на источник страницы, например, на Белорусских станциях , вы увидите список доступных станций:

<select name="cccc">
    <option selected>Select a location
    <OPTION VALUE="UMBB"> Brest
    <OPTION VALUE="UMGG"> Gomel'
    <OPTION VALUE="UMMG"> Grodno
    <OPTION VALUE="UMMM"> Loshitsa / Minsk International 1
    <OPTION VALUE="UMMS"> Minsk
    <OPTION VALUE="UMII"> Vitebsk
</select>

Вы можете видеть, что теги «OPTION» не закрыты. Параметры по умолчанию в HtmlAgilityPack закрывают теги следующим образом:

<select name="cccc">
    <option selected>Select a location
    <OPTION VALUE="UMBB"> Brest
    <OPTION VALUE="UMGG"> Gomel'
    <OPTION VALUE="UMMG"> Grodno
    <OPTION VALUE="UMMM"> Loshitsa / Minsk International 1
    <OPTION VALUE="UMMS"> Minsk
    <OPTION VALUE="UMII"> Vitebsk
    </OPTION></OPTION></OPTION></OPTION></OPTION></OPTION></OPTION>
</select>

Который делает это болью для разбора или прохождения. Я придумал следующий метод для рекурсии каждого тега, но мне интересно, есть ли более элегантный способ, возможно, используя LINQ?

Мой метод:

private static void GetStations(HtmlNode node, ref Dictionary<string, string> stations)
{
    // the HTML is malformed, such that the <option> elements are
    // not properly closed, so we have to parse manually
    string name = node.GetAttributeValue("value", string.Empty).Trim();
    string value = node.InnerHtml.Substring(0, node.InnerHtml.IndexOf("\n")).Trim();

    if (!string.IsNullOrEmpty(name) &&
             name.Length == 4 &&
            char.IsUpper(name[0]))
    {
        stations.Add(name, value);
    }
    // due to not closing the <option> elements
    // we have to recurse into child nodes until
    // we get them all
    if (node.HasChildNodes)
    {
        GetStations(node.LastChild, ref stations);
    }
}

Что называется так:

Dictionary<string, string> sites = new Dictionary<string, string>();
...
foreach (HtmlNode option in select.ChildNodes)
{
    if ((option.Name == "option") && (option.HasAttributes))
    {
        GetStations(option, ref sites);
    }
}

Я чувствую, что использую метод грубой силы, чтобы получить список станций, и я мог бы пропустить некоторые из возможностей библиотеки HtmlAgilityPack. Есть ли способ лучше? Существуют ли настройки, которые могут сделать это не проблемой? Может ли LINQ справиться с этим более легко?

Я пытаюсь использовать XPATH, поскольку это самый простой механизм для получения подмножества тегов. Однако из-за того, что теги не закрываются, я получаю каждый тег опции на странице, в то время как я хочу только те, которые находятся в теге «select». Итак, один квалификатор, как вы можете видеть, состоит в том, что теги «option», которые я хочу, имеют значение @ value = 'XXXX', где «XXXX» - это 4-значный идентификатор ячейки верхнего регистра. Есть ли способ указать, что я хочу только теги опций в документе, у которых есть атрибут с именем «значение» с четырехзначным значением в верхнем регистре? Могу ли я передать функцию сравнения в инструкцию xpath?

Принятый ответ

Спасибо за все указатели. Я сделал больше поисков синтаксиса xpath и нашел, что это работает:

//select[@name='cccc']/descendant::option[@value]

это дает мне все теги «option» под тегом «select» с атрибутом @ name = 'cccc, где «тег option имеет атрибут @value.

Гораздо меньше работы, чем то, что я делал. Теперь, чтобы реорганизовать весь мой другой код, который проходит через DOM с помощью HAP и посмотреть, как XPATH может облегчить мою жизнь!


Популярные ответы

HtmlAgilityPack может автоматически исправить закрывающий тег, но, возможно, не совсем так, как вы ожидаете :

HtmlNode.ElementsFlags["option"] = HtmlElementFlag.Closed;
var doc = new HtmlDocument();
doc.LoadHtml(html);

Во всяком случае, в этот момент вы все равно можете выбрать текст, который должен находиться в <option> используя XPath next following-sibling::text()[1] , например:

var optionTexts = doc.DocumentNode.SelectNodes("//select[@name='cccc']/option/following-sibling::text()[1]");
foreach (HtmlNode node in optionTexts)
{
    Console.WriteLine(node.InnerText);
}


Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему
Лицензировано согласно: CC-BY-SA with attribution
Не связан с Stack Overflow
Является ли этот КБ законным? Да, узнайте, почему