Pull to refresh

PowerShell, HTML Agility Pack: получение значения атрибута HTML-элемента

Reading time 8 min
Views 1.3K

Мне интересен атрибут «class» HTML-элементов, поэтому в примерах буду работать с этим атрибутом. Но изложенное в этой статье, по идее, можно применить к любому атрибуту HTML-элементов.

Примеры написаны на языке PowerShell и предполагаются для работы в программах-оболочках «Windows PowerShell» версии 5.1 и «PowerShell» версии 7 (я работаю в операционной системе «Windows 10»). Для работы с HTML-деревом использую библиотеку «HTML Agility Pack» версии 1.11.43.

Итак, получаем коллекцию узлов HTML-документа. Это можно сделать разными способами. Например, можно просто получить коллекцию всех узлов HTML-документа. Я описывал, как это можно сделать, в статье «PowerShell: обход и визуализация HTML-дерева из файла». При переборе коллекции узлов HTML-документа помещаем очередной узел в переменную $node. В упомянутой статье я также показывал, как при переборе коллекции узлов HTML-документа можно отобрать узлы, являющиеся HTML-элементами.

Прямой доступ к значениям атрибутов

Получить значение атрибута «class» HTML-элемента в скрипте на языке PowerShell можно примерно так:

if ($node.HasAttributes) {
    $classValue = $node.Attributes["class"]
    if ($classValue) {
        #   Работаем с $classValue.Value...
        #   ...
    }
}

Нужно иметь в виду, что выражение $node.Attributes["class"] возвращает пока что не требуемое значение атрибута, а объект класса HtmlAgilityPack.HtmlAttribute. Требуемое значение атрибута можно получить из свойства Value этого объекта, то есть в коде выше — с помощью выражения $classValue.Value.

В случае отсутствия у HTML-элемента атрибута с указанным именем (в нашем случае — с именем «class») выражение $node.Attributes["class"] возвращает значение NULL, поэтому нужна проверка if ($classValue).

С помощью кода из примера выше можно отследить, например, атрибуты с названием «class» HTML-элементов, не содержащие значения или содержащие только пробельные символы (пробел, символ горизонтальной табуляции, символы новой строки). Это можно сделать с помощью следующего дополнения на языке PowerShell (вставляем его внутрь кода выше):

        if ("" -eq $classValue.Value) {
            "Атрибут class ничего не содержит. Не ошибка, но зачем?"
        } elseif ("" -eq $node.GetClasses()) {
            "Атрибут class не содержит слов. Не ошибка, но зачем?"
        }

Для очистки значения атрибута от лишних пробельных символов в коде выше использован метод GetClasses библиотеки HTML Agility Pack. Это не обязательно. Можно было использовать метод String.Trim платформы «.NET».

Условие if ("" -eq $classValue.Value) отловит случаи вида <p class></p> и <p class=""></p>. Условие elseif ("" -eq $node.GetClasses()) отловит случаи, когда значение атрибута «class» содержит только пробельные символы (пробел, символ горизонтальной табуляции, символы новой строки).

Доступ к значениям атрибутов с помощью методов GetAttributeValue

Библиотека HTML Agility Pack предоставляет для узлов HTML-дерева четыре вспомогательных метода GetAttributeValue для доступа к значениям атрибутов HTML-элементов. У этих методов следующие сигнатуры:

  1. string GetAttributeValue(string name, string def)

  2. int GetAttributeValue(string name, int def)

  3. bool GetAttributeValue(string name, bool def)

  4. T GetAttributeValue<T>(string name, T def)

Для программистов на языке PowerShell это выглядит странно, но следует иметь в виду, что библиотека HTML Agility Pack написана на языке C#. Одна из ключевых разниц между этими языками программирования состоит в том, что язык PowerShell — это язык с динамической типизацией (тип привязывается к переменной в момент присваивания значения), а C# — язык со статической типизацией (тип привязывается к переменной в момент объявления и не может быть изменен для этой переменной позже).

На практике это выражается, в частности, в том, что на языке PowerShell мы можем создать одну функцию (метод), которая может принимать переменную любого типа и возвращать переменную любого типа. А на языке C# мы этого сделать не можем, поэтому для имитации такого поведения приходится создавать несколько методов с одним и тем же названием, но разной сигнатурой (со входящими параметрами разного типа и возвращаемым значением разного типа). По-научному способность функции обрабатывать данные разных типов называется «полиморфизмом». Поэтому в библиотеке были созданы методы с первыми тремя сигнатурами из списка выше.

Впрочем, в языке C# существуют так называемые «обобщенные методы» (по-английски «generic method»). С помощью синтаксиса обобщенного метода можно обобщить несколько методов с одним и тем же названием, но разными типами входящих параметров и возвращаемого значения, в один метод. В библиотеке HTML Agility Pack это реализовано четвертым вариантом метода GetAttributeValue (см. в списке выше). Этот вариант был добавлен в библиотеку в 2020 году. На сегодня в вариантах библиотеки для некоторых устаревающих реализаций платформы «.NET» используются три первых варианта метода GetAttributeValue, а в вариантах библиотеки для более новых реализаций платформы «.NET» используется четвертый вариант метода GetAttributeValue. В этом можно убедиться, просмотрев код библиотеки на сайте «GitHub».

У метода GetAttributeValue два входных параметра (оба — обязательные). Через первый параметр передается название атрибута, значение которого нужно получить. Через второй — значение, которое функция вернет, если атрибут с указанным именем не будет найден на текущем узле HTML-дерева.

В принципе, эти методы под капотом используют прямой способ доступа к атрибутам HTML-элементов, описанный в начале этой статьи. Так что можно считать, что они введены в библиотеку HTML Agility Pack для упрощения кода программ, то есть для красоты.

Следует иметь в виду, что, поскольку второй параметр может быть возвращен методом GetAttributeValue, то тип возвращаемого значения должен совпадать с типом второго параметра. Это следует из сказанного выше насчет статической типизации в языке C#.

Таким образом, не получится, например, задать в качестве второго параметра значение $false, а в качестве первого параметра значение "class", и при этом ожидать, что метод вернет либо значение атрибута «class» в виде строки, либо значение $false в случае отсутствия атрибута «class». Метод в языке C# не может возвращать либо строку, либо значение $false логического типа (он может возвращать только значение какого-то одного типа)! Поэтому, если нужный атрибут найден, а тип второго параметра не совпадает со строчным типом, то значение атрибута будет преобразовано в значение с типом, совпадающим с типом второго параметра метода GetAttributeValue.

Как тогда пользоваться этим методом? Рассмотрим на примерах.

Метод GetAttributeValue для логического типа

Как я понимаю, если атрибут HTML-элемента может содержать в своем значении строку «false» или строку «true», то можно вторым параметром метода GetAttributeValue задавать значение логического типа (либо $true, либо $false). В случае, если атрибут содержит строку с любым другим текстом, будет возвращено значение второго параметра.

Вызываем метод GetAttributeValue на языке PowerShell со вторым параметром логического типа $false:

$classValue = $node.GetAttributeValue("class", $false)

Вызов со значением $null работает так же, как и вызов со значением $false (подробнее о том, почему это так, можно прочитать в соответствующем моем вопросе и ответе на него на сайте «Stack Overflow»):

$classValue = $node.GetAttributeValue("class", $null)

Результаты для разных HTML-элементов:

<p class="true"></p>               True  (преобразование в логический тип)
<p class="false"></p>              False (преобразование в логический тип)
<p></p>                            False (возврат второго параметра)
<p class="любой другой текст"></p> False (исключение, возврат второго параметра)

В коде выше для первого и второго HTML-элементов (если считать сверху) возвращается значение атрибута, преобразованное из строки в значение логического типа. Для третьего HTML-элемента возвращается значение по умолчанию (второй параметр метода), в данном случае это $false, потому что атрибута с названием «class» не найдено. Для четвертого HTML-элемента возвращается значение по умолчанию (второй параметр метода), в данном случае это $false, потому что преобразование значения атрибута «class» в логический тип закончилось неудачно и было выброшено исключение. В исходном коде библиотеки (на языке C#) это оформлено примерно так:

    try
    {
        return Convert.ToBoolean(att.Value);
    }
    catch
    {
        return def;
    }

Попробуем задать вторым параметром значение $true (на языке PowerShell):

$classValue = $node.GetAttributeValue("class", $true)

Результаты для разных HTML-элементов:

<p class="true"></p>               True  (преобразование в логический тип)
<p class="false"></p>              False (преобразование в логический тип)
<p></p>                            True  (возврат второго параметра)
<p class="любой другой текст"></p> True  (исключение, возврат второго параметра)

Тут следует отметить, что правила преобразования строки в значение логического типа для меня несколько непривычные. Я привык, что при таком преобразовании значение False возвращается, только если строка — пустая (такое преобразование описано для языка PowerShell). Иначе (для непустых строк) возвращается значение True.

В данном случае, внутри метода GetAttributeValue, используется преобразование строки в значение логического типа посредством метода Convert.ToBoolean платформы «.NET». Этот метод, как уже было показано выше, воспринимает только слово «true» внутри исходной строки в качестве значения True или слово «false» внутри исходной строки в качестве значения False (регистр букв значения не имеет, ведущие и заключающие пробельные символы игнорируются). Если этих слов в исходной строке найдено не будет, данный метод выбросит исключение (ошибку), которую следует, конечно же, отлавливать для обработки, как было показано выше.

Метод GetAttributeValue для числового типа

Вызываем метод GetAttributeValue на языке PowerShell со вторым параметром числового типа:

$classValue = $node.GetAttributeValue("class", 0)

Результаты для разных HTML-элементов:

<p class="42"></p>                   42 (преобразование в целое со знаком)
<p class="-135"></p>               -135 (преобразование в целое со знаком)
<p class="0"></p>                     0 (преобразование в целое со знаком)
<p class="52.34"></p>                 0 (исключение, возврат второго параметра)
<p></p>                               0 (возврат второго параметра)
<p class="любой другой текст"></p>    0 (исключение, возврат второго параметра)

Для преобразования значения атрибута в число используется метод Convert.ToInt32 платформы «.NET». То есть метод GetAttributeValue в таком виде можно использовать для атрибутов, которые содержат строку в формате целого числа, положительного или отрицательного.

Метод GetAttributeValue для строкового типа

Вызываем метод GetAttributeValue на языке PowerShell со вторым параметром строкового типа:

$classValue = $node.GetAttributeValue("class", "не найдено")

Результаты для разных HTML-элементов:

<p class="любой текст"></p> любой текст (значение атрибута)
<p class="true"></p>        true        (значение атрибута)
<p class="42"></p>          42          (значение атрибута)
<p class="не найдено"></p>  не найдено  (значение атрибута)
<p></p>                     не найдено  (возврат второго параметра)

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

Заключение

Методы GetAttributeValue для узлов HTML-дерева, предоставляемые библиотекой HTML Agility Pack, можно использовать, но работают они довольно специфически. Следует учитывать возможное преобразование значения запрашиваемого атрибута HTML-элемента в значение типа, совпадающего с типом второго параметра метода GetAttributeValue. Лично мне больше нравится прямой доступ к значениям атрибутов HTML-элементов, описанный в начале этой статьи.

Tags:
Hubs:
+1
Comments 0
Comments Leave a comment

Articles