Продолжаю писать скрипт для анализа текстового файла с кодом на языке HTML. Скрипт должен работать в программе-оболочке «Windows PowerShell» версии 5.1 (и в «PowerShell» версии 7) в операционной системе «Windows 10». Ранее я вывел в консоль схему HTML-дерева из текстового файла с кодом на языке HTML (в кодировке UTF-8 без метки BOM).
Вывод атрибута class HTML-элементов
После этого я решил вывести на схеме HTML-дерева названия классов CSS для HTML-элементов, у которых есть атрибут class
. Также я решил, что эти названия должны быть подсвечены, потому что в данный момент моя цель — анализ именно названий классов CSS.
Библиотека «HTML Agility Pack», которую я использую, дает возможность запросить названия классов CSS для каждого узла HTML-дерева с помощью метода GetClasses
. Подсветить названия классов можно с помощью управляющих кодов ANSI (я буду подсвечивать названия классов, меняя цвет текста).
Меняю код обработки узла при обходе HTML-дерева:
# Обработка узла (вывод в консоль)
$esc = [char]27 # Работает в «Windows PowerShell» версии 5.1
$indent = 2 # Отступ: 2 пробела на уровень, можно менять
if (("#document" -eq $node.Name) -or
(("#" -eq $node.Name[0]) -and ("" -eq $node.OuterHTML.Trim())) -or
("#" -eq $node.Name[0])) {
# 1. Корневой узел (#document) не выводим,
# 2. Пустые узлы (пробелы, символы новой строки и т.п.) не выводим,
# 3. Комментарии (#comment) и текстовые (#text) узлы не выводим
# (третье условие можно убрать, тогда задействуется переменная $content)
} else {
# Для непустых комментариев и текстовых узлов выводим их содержимое
$content = ""; if ("#" -eq $node.Name[0]) {
$content = ", '" + $node.OuterHTML.Trim() + "'" }
# Вычисление отступа узла для визуализации глубины
$count = 0; foreach ($ancestor in $node.Ancestors()) { $count++ }
$count = ($count - 1) * $indent
# Получение и подсветка атрибута class узла HTML-дерева
$classes = ""; if ("" -ne $node.GetClasses()) {
$color = "33" # Желтый (слот цветовой схемы), можно менять
$classes = ".$esc[$color`m" + $node.GetClasses() + "$esc[0m"
}
(" " * $count) + $node.Name + $classes + $content
# Перебор названий классов
# ...
}
Я добавил переменную $classes
и ее обработку. От названия узла (HTML-элемента) названия классов отделяются точкой. С помощью одного из управляющих кодов ANSI названия классов подсвечиваются желтым цветом, взятым из слота «Желтый» палитры текущей цветовой схемы, выбранной пользователем эмулятора терминала. Управляющий символ определен выражением [char]27
, а не выражением `e
или `u{1B}
, потому что последние появились в программе-оболочке «PowerShell» с версии 6, а в программе-оболочке «Windows PowerShell» версии 5.1 не работают.
Такая подсветка цветом с помощью управляющих кодов ANSI работает как в программе-«эмуляторе терминала» «Windows Terminal», так и в программе-«эмуляторе терминала» «Windows Console».
Я пока убрал узлы-комментарии и текстовые узлы из вывода, так как сейчас меня интересуют HTML-элементы и классы CSS, привязанные к этим HTML-элементам.
Перебор классов на одном узле
Сначала я думал как-то разбирать строку, полученную из метода GetClasses
узла. А потом я подумал, что этот метод не просто так, наверное, называется «получить классы» в переводе на русский. Так и оказалось: этот метод возвращает не строку, а коллекцию классов, которую можно перебрать, например, в цикле foreach
.
Почему значение атрибута class
HTML-элемента требует разбора? Хоть атрибут называется просто «класс» в переводе на русский, он может содержать не одно название класса CSS, а ряд названий классов CSS, разделенных пробельными символами. В пробельные символы, кстати, входит не только пробел, но и горизонтальная табуляция, и символы новой строки. Из-за этого названия классов можно не только писать одной строкой, разделяя пробелом разные названия, а располагать названия классов в столбик, для лучшей читаемости.
Например, вот названия классов CSS перечислены в одной строке и разделены пробелом в коде на языке HTML:
<div class="class-name1 class-name2 class-name3">
<!-- содержимое блока -->
</div>
А ниже названия классов CSS перечислены в нескольких строках и разделены пробелами, символами горизонтальной табуляции и символами новой строки (это тоже код на языке HTML):
<div class="class-name1
class-name2
class-name3">
<!-- содержимое блока -->
</div>
В вышеприведенном коде перед названием класса class-name2
введены три символа горизонтальной табуляции, а перед названием класса class-name3
— 12 символов пробела. В хорошем редакторе кода должна быть возможность настройки ввода отступов — символами горизонтальной табуляции или символами пробела. В последнем случае при нажатии клавиши «Tab» на клавиатуре вводится не символ горизонтальной табуляции, а пробелы, количество которых тоже может быть настроено (обычно это 2 или 4 пробела).
Кстати, для публикации кода в интернете я предпочитаю вводить отступы в коде только пробелами. Символы горизонтальной табуляции на разных сайтах отображаются по-разному. У меня в редакторе «Visual Studio Code» в вышеприведенном коде названия классов отображаются в ровный столбик, так как три символа табуляции (каждый шириной в 4 пробела) по ширине соответствуют 12 символам пробела. А при публикации этого кода на Хабре (в новом редакторе) название класса class-name2
оказалось сдвинуто влево. Как я понимаю, из-за того, что Хабр придает символам горизонтальной табуляции ширину в два символа пробела. (После окончательной публикации статьи названия классов в столбце таки выравниваются.)
При публикации кода, к примеру, на «GitHub» символ горизонтальной табуляции представляется шириной в 8 символов пробела, из-за чего код, наоборот, оказывается сдвинутым вправо.
Вернемся к нашей теме. Итак, к приведенному выше в этой статье коду добавим следующее:
# Перебор названий классов
foreach ($class in $node.GetClasses()) {
$color = "36" # Голубой (слот цветовой схемы), можно менять
(" " * ($count + $indent)) + "$esc[$color`m" + $class + "$esc[0m"
}
Тестирование
Я подготовил файл с кодом на языке HTML для тестирования своего скрипта:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>Тестовая страница</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="class-name1 class-name2 class-name3">
В одну строку, разделитель: символ пробела.
</div>
<div class="class-name1
class-name2
class-name3">
В несколько строк (в столбик), разделители: пробелы,
символы горизонтальной табуляции, символы новой строки.
</div>
<div class=" class-name1 class-name2 class-name3">
В одну строку. Есть пробельные символы в начале.
</div>
<div class="class-name1 class-name2 class-name3 ">
В одну строку. Есть пробельные символы в конце.
</div>
</body>
</html>
В этом файле есть четыре блока div
с атрибутом class
. Они содержат разные варианты ввода названий классов CSS для блока. Этот файл успешно прошел проверку в известном валидаторе HTML, то есть все эти варианты валидны.
Метод GetClasses
узла из библиотеки «HTML Agility Pack» во всех этих четырех вариантах выдаст одинаковый результат. То есть ненужные разделители в виде вышеупомянутых пробельных символов (пробелы, символы горизонтальной табуляции, символы новой строки) будут откинуты (будут заменены одним пробелом в местах, где требуется разделитель), а мы получим коллекцию «очищенных» названий классов CSS для перебора и анализа.
Вот как у меня выглядит вывод в консоль для вышеуказанного тестового файла:
Обработка ссылок на символы
Как известно, значение атрибута class
HTML-элементов может состоять из текста (символы Юникода) с вкраплениями ссылок на символы вида ю
(это пример ссылки на строчную русскую букву «ю»). Ссылки на символы кроме числа (десятичный код символа в Юникоде) еще могут содержать названия символов из таблицы, приведенной в стандарте HTML.
Нужно иметь в виду, что показанный выше метод GetClasses
узла из библиотеки «HTML Agility Pack» не обрабатывает ссылки на символы, встречающиеся в названиях классов. Что значит «не обрабатывает»? Это значит, что он не преобразовывает ссылки на символы в символы, которые эти ссылки на символы представляют.
А зачем преобразовывать ссылки на символы в символы? Приведу один пример. Предположим, в файле с кодом на языке HTML есть такой HTML-элемент:
<div class="имя-класса1 имя класса2">
<!-- содержимое блока -->
</div>
Сколько тут классов CSS? На первый взгляд кажется, что к этому HTML-элементу привязано два класса CSS: имя-класса1
и имя класса2
. Метод GetClasses
тоже так считает и возвращает коллекцию из двух указанных имен классов.
Однако, при проверке в браузере (я использую для тестирования браузер «Microsoft Edge» на движке «Chromium» и браузер «Google Chrome») выясняется, что браузер видит здесь три класса: имя-класса1
, имя
и класса2
. Почему? Потому что ссылка на символ  
представляет символ пробела, а символ пробела является одним из возможных разделителей имен классов в атрибуте class
HTML-элементов согласно стандарта HTML!
Я хочу, чтобы мой скрипт показал и исходную строку с именами классов, содержащую не конвертированные в символы ссылки на символы, и перебор имен классов с учетом конвертирования в символы ссылок на символы. Как это сделать?
Библиотека «HTML Agility Pack» имеет в своем составе служебный класс (по-английски «utility class») HtmlAgilityPack.HtmlEntity
, с помощью которого можно выполнить, в частности, конвертацию строки со ссылками на символы в строку, в которой все ссылки на символы заменены на представляемые ими символы. Это можно сделать с помощью метода DeEntitize
. Данный метод работает как с числовыми ссылками на символы, так и с именованными ссылками на символы. Объект указанного класса для этого создавать не нужно.
Дополним код перебора названий классов:
# Перебор названий классов
$classes = [HtmlAgilityPack.HtmlEntity]::DeEntitize($node.GetClasses())
if ($null -ne $classes) {
$node.RemoveClass()
$node.AddClass($classes)
}
foreach ($class in $node.GetClasses()) {
$color = "36" # Голубой (слот цветовой схемы), можно менять
(" " * ($count + $indent)) + "$esc[$color`m" + $class + "$esc[0m"
}
До этого кода мы уже вывели в консоль исходное значение атрибута class
HTML-элемента. Теперь просто удаляем старое значение этого атрибута и заменяем его на значение, в котором все ссылки на символы конвертированы в представляемые ими символы. После этой операции воспользуемся методом GetClasses
библиотеки «HTML Agility Pack» как ни в чем не бывало. Таким образом, получается, что мы немного доработали функциональность библиотеки.
Вот как работа скрипта выглядит у меня в консоли:
Резюмируем
Для работы с атрибутом class
HTML-элементов в библиотеке «HTML Agility Pack» есть метод GetClasses
. С его помощью можно получить значение атрибута class
одной строкой, в которой убраны лишние символы-разделители, а, где это необходимо, оставлен один разделитель в виде символа пробела. Также с помощью метода GetClasses
можно получить коллекцию имен классов CSS из значения атрибута class
HTML-элемента. Эту коллекцию можно перебирать в цикле foreach
для анализа отдельных имен классов CSS.
Метод GetClasses
не конвертирует ссылки на символы в представляемые ими символы. Для такой конвертации можно воспользоваться методом DeEntitize
служебного класса HtmlAgilityPack.HtmlEntity
.