PowerShell, HTML Agility Pack: связь с исходным HTML-файлом при его анализе
В скрипте для программы-оболочки «Windows PowerShell» версии 5.1 (или «PowerShell» версии 7) в операционной системе «Windows 10» я получаю текст из файла с кодом на языке HTML для дальнейшего анализа. С помощью библиотеки «HTML Agility Pack» превращаю этот текст в объект $dom
, содержащий HTML-дерево, представляющее исходный файл.
После этого я задумался о том, что, если я при анализе HTML-дерева найду какую-нибудь ошибку, мне понадобится вывести сообщение об этой ошибке в консоль. А в этом сообщении хотелось бы указать местонахождение ошибки в исходном файле. То есть хотелось бы указать номер строки исходного файла, в которой содержится ошибка, а также номер позиции в строке (номер столбца), с которого начинается узел HTML-дерева, содержащий ошибку.
Эта информация должна быть получена при конвертации исходного файла в объект $dom
. Сначала я хотел сам написать это, а затем догадался посмотреть в описании библиотеки «HTML Agility Pack» — в ней уже есть эта функциональность! У каждого узла есть свойства Line
и LinePosition
, содержащие нужные номер строки и номер столбца (позиции в строке).
Я временно изменил код обработки узла при обходе HTML-дерева, чтобы увидеть эти номера:
# Обработка узла (вывод в консоль)
if (("#document" -eq $node.Name) -or
(("#" -eq $node.Name[0]) -and ("" -eq $node.OuterHTML.Trim()))) {
# Корневой узел не выводим,
# Пустые узлы (пробелы, символы новой строки и т.п.) не выводим
} else {
$content = ""; if ("#" -eq $node.Name[0]) {
$content = ", '" + $node.OuterHTML.Trim() + "'" }
$count = 0; foreach ($ancestor in $node.Ancestors()) { $count++ }
$count = ($count - 1) * 2 # отступ: 2 пробела на уровень, можно менять
(" " * $count) + $node.Name + $content + ", строка " +
$node.Line + ", столбец " + ($node.LinePosition + 1)
}
После этого скрипт выдал у меня такой результат (тестовый исходный файл с кодом на языке HTML можно увидеть в одной из предыдущих статей, он примитивный):
#comment, '', строка 1, столбец 1
html, строка 1, столбец 17
head, строка 1, столбец 24
meta, строка 1, столбец 33
title, строка 1, столбец 58
#text, 'Название страницы', строка 1, столбец 65
body, строка 1, столбец 99
#comment, '', строка 1, столбец 108
p, строка 1, столбец 139
#text, 'Текст.', строка 1, столбец 142
Видно, что что-то пошло не так: для всех узлов HTML-дерева указана одна и та же строка с номером 1, хотя в исходном файле узлы находятся в строках с разными номерами.
Через некоторое время я сообразил: я сам получаю текст из исходного файла в переменную $html
типа System.String
. То есть я сам преобразовал текст исходного файла в строку, потеряв данные о структуре исходного файла. Кроме метода LoadHtml
объекта класса HtmlAgilityPack.HtmlDocument
, который принимает аргумент типа System.String
, в библиотеке «HTML Agility Pack» у объекта класса HtmlAgilityPack.HtmlDocument
есть и другие методы для загрузки исходного кода на языке HTML.
Например, один из них называется Load
и может принимать строку с путем к исходному файлу и сведения о его кодировке. Этот метод сам получит данные из исходного файла и правильно прочитает информацию о его структуре (номера строк и номера столбцов). Использование этого метода даже немного упростит мой скрипт: мне не нужно будет самому получать данные из исходного файла.
Итак, вносим изменение в скрипт (переменная $file
содержит строку (System.String
) с путем к исходному файлу, путь может быть как абсолютным, так и относительным):
Add-Type -Path "HtmlAgilityPack.1.11.43\lib\netstandard2.0\HtmlAgilityPack.dll"
$dom = New-Object -TypeName "HtmlAgilityPack.HtmlDocument"
#$dom.LoadHtml($html) # Вариант с загрузкой строки
$dom.Load($file, [System.Text.Encoding]::UTF8)
После этого изменения скрипт выдал у меня такой результат:
#comment, '', строка 1, столбец 1
html, строка 2, столбец 1
head, строка 3, столбец 1
meta, строка 4, столбец 3
title, строка 5, столбец 3
#text, 'Название страницы', строка 5, столбец 10
body, строка 7, столбец 1
#comment, '', строка 8, столбец 3
p, строка 9, столбец 3
#text, 'Текст.', строка 9, столбец 6
Теперь всё в порядке, можно проверить по исходному файлу:
Название страницы
Текст.
Замечу, что свойство Line
узла HTML-дерева выдает нумерацию строк, начиная с единицы, а вот свойство LinePosition
выдает нумерацию позиций в строке, начиная с нуля. Поэтому в скрипте пришлось к номеру позиции в строке (номеру столбца) каждый раз прибавлять единицу (см. код выше в статье).
Заключение
Эта информация будет полезна тем, кто, как и я, начинает работать с библиотекой «HTML Agility Pack». Лично мне было сложно выбрать между методами LoadHtml
и Load
для загрузки исходного кода на языке HTML, так как я изначально не понимал, какая между ними может быть разница (кроме той, что эти методы принимают разные входные параметры). Выше я продемонстрировал одно из отличий. Возможно, есть и другие.
Документация по библиотеке «HTML Agility Pack» на сайте проекта очень бедна и неполна. Постоянно приходится шерстить файл «HtmlAgilityPack.xml» с описанием классов, содержащихся в библиотеке (этот файл довольно большой, я получил его вместе с библиотекой).