← Весь справочник

xmllint

Проверка, форматирование, валидация по схеме и XPath-запросы к XML — --format, --xpath, string(), count()

xmllint из libxml2 проверяет, что XML правильно сформирован, валидирует его по DTD или XSD-схеме, красиво форматирует и достаёт данные XPath-запросами. Для XML это то же, что jq для JSON.

Синтаксис

xmllint [опции] файл.xml…

Вместо имени файла можно указать -, тогда XML читается из stdin. Без опций xmllint разбирает документ и печатает его обратно — так видны ошибки разбора.

Проверка и валидация

ОпцияЧто делает
--nooutне печатать документ — только сообщить об ошибках
--validпроверить по DTD, объявленному в самом документе (<!DOCTYPE …>)
--dtdvalid файл.dtdпроверить по внешнему DTD
--schema файл.xsdпроверить по XML Schema (XSD)
--relaxng файл.rngпроверить по схеме RELAX NG
--recoverпри ошибках разбора вывести то, что удалось разобрать
--htmlразбирать как HTML (прощает незакрытые теги)
--nonetне скачивать DTD и сущности из сети
--hugeснять внутренние лимиты на размер документа

Вывод и преобразование

ОпцияЧто делает
--formatвывести с отступами; ширину отступа задаёт переменная XMLLINT_INDENT (по умолчанию два пробела)
--noblanksубрать незначащие пробелы и переводы строк — XML в одну строку
--output файл (-o)записать результат в файл вместо stdout
--encode КОДИРОВКАвывести в другой кодировке
--c14nканоническая форма: атрибуты, кавычки и пустые теги в едином виде; вместе с --noblanks удобно сравнивать документы через diff
--dropdtdубрать <!DOCTYPE …>
--noentподставить значения сущностей
--xincludeвыполнить <xi:include>
--nscleanубрать лишние объявления пространств имён

Запросы

ОпцияЧто делает
--xpath 'выражение'выполнить XPath и напечатать результат (документ при этом не печатается)
--shellинтерактивная оболочка: cat //title, ls, cd, exit

XPath: пути

ВыражениеЧто выбирает
/catalog/bookэлементы book прямо внутри корня catalog
//titleвсе title на любой глубине
*, @*любой элемент / любой атрибут
//book/@idатрибуты id
//title/text()текст внутри элементов, без тегов
..родитель: //title[../@id="3"]
//book[@lang="ru"]отбор по атрибуту
//book[price > 8]отбор по значению дочернего элемента
//book[1], //book[last()], [position() < 3]по номеру у своего родителя (с 1), последний, первые два
(//title)[2]второй title во всём документе
a | bобъединение двух наборов узлов
parent::, ancestor::, following-sibling::, preceding-sibling::оси: родитель, предки, соседи после / до

XPath: функции и операторы

ФункцияЧто делает
string(…)текстовое значение первого найденного узла
count(…), sum(…)количество узлов, сумма значений
contains(a, b), starts-with(a, b)подстрока, префикс
normalize-space(…)обрезать пробелы по краям и схлопнуть внутренние
concat(a, b, …), string-length(…), translate(s, abc, ABC)склейка, длина, посимвольная замена
name(…), local-name(…)имя элемента с префиксом / без префикса пространства имён
not(…), boolean(…)отрицание, «нашлось ли»
and, or, =, !=, <, >логика и сравнения в условиях […]

Коды выхода

0 — всё в порядке, 1 — ошибка разбора (XML сломан) или файл не найден, 3 / 4 — документ не прошёл валидацию по схеме / DTD, 10 — XPath ничего не нашёл или выражение с ошибкой.

Примеры

cat > books.xml <<'EOF'
<catalog><book id="1" lang="en"><title>Dune</title><price>9.99</price></book><book id="2" lang="ru"><title>Solaris</title><price>7.50</price></book></catalog>
EOF

# проверить синтаксис: молчит, если всё хорошо
xmllint --noout books.xml && echo OK

# красиво отформатировать
xmllint --format books.xml

# элементы целиком, с тегами
xmllint --xpath '//title' books.xml

# только текст — по строке на узел
xmllint --xpath '//title/text()' books.xml

# одно значение: заголовок книги с id=2
xmllint --xpath 'string(//book[@id="2"]/title)' books.xml

# значение атрибута без обёртки id="…"
xmllint --xpath 'string(//book[1]/@lang)' books.xml

# сколько книг и сумма цен
xmllint --xpath 'count(//book)' books.xml
xmllint --xpath 'sum(//price)' books.xml

# поиск по части текста
xmllint --xpath '//book[contains(title, "un")]/@id' books.xml

# проверить документ по XSD-схеме
xmllint --noout --schema catalog.xsd books.xml

# XML из stdin
echo '<a><b>1</b></a>' | xmllint --format -

# сравнить два документа без учёта форматирования
diff <(xmllint --noblanks --c14n a.xml) <(xmllint --noblanks --c14n b.xml)

Подводные камни

  • string() берёт только первый узел. string(//title) вернёт Dune, а не все названия. Для всех значений используй //title/text(), для N-го — string((//title)[N]).
  • //book[2] — не «вторая книга в документе», а каждая книга, которая вторая у своего родителя. Для сквозной нумерации нужны скобки: (//book)[2].
  • //book/@id печатает id="1" вместе с именем атрибута. Чтобы получить только значение, бери string(…/@id).
  • Пустой результат — это ошибка. Если XPath ничего не нашёл, xmllint пишет XPath set is empty и завершается с кодом 10. А string() от пустого набора — просто пустая строка и код 0.
  • Пространства имён. Если у корня есть xmlns="…", запрос //v ничего не найдёт, а //p:v даёт Undefined namespace prefix. Обходной путь: //*[local-name()="v"].
  • Пробелы в тексте сохраняются. <title> Dune </title> вернёт значение с пробелами — сравнение = не сработает. Используй normalize-space().
  • Кавычки. Выражение бери в одинарные кавычки, а строки внутри XPath — в двойные: '//book[@lang="ru"]'.
  • В старых версиях libxml2 результаты text() склеиваются без перевода строки. Если значения слиплись, перебирай узлы по индексу.

См. также

jq · yq · grep · sed · iconv