XML: что это такое, как выглядит XML-файл и как с ним работать

XML: что это такое, как выглядит XML-файл и как с ним работать Полезное

XML (eXtensible Markup Language) — это текстовый формат разметки для хранения и обмена структурированными данными. Расширяемый он потому, что набор тегов не задан заранее: имена элементов придумывает автор документа под свою задачу. XML 1.0 принят как рекомендация W3C в 1998 году и до сих пор живёт в конфигурациях, документах Office, SVG, RSS и обмене между системами. Ниже — разбор примера, правила корректного документа, схемы, сравнение с JSON и YAML и работа с XML в Python.

Что такое XML-файл и чем его открыть

XML-файл — обычный текстовый файл, как правило с расширением .xml, внутри которого данные размечены тегами. Открыть его можно любым текстовым редактором (Блокнот, VS Code, Notepad++) или браузером — он покажет дерево документа и укажет на ошибку разметки. Программы читают XML парсером — библиотекой, превращающей текст в дерево объектов.

Кодировка объявляется в первой строке; без объявления парсер считает документ UTF-8 (UTF-16 распознаётся по маркеру BOM). Русский текст стоит хранить в UTF-8 и объявлять это явно.

Пример XML-документа с разбором

<?xml version="1.0" encoding="UTF-8"?>
<!-- Каталог небольшой библиотеки -->
<library>
  <book id="1" lang="ru">
    <title>Основы XML</title>
    <year>2026</year>
  </book>
  <book id="2" lang="en">
    <title>XML in Practice</title>
    <year>2024</year>
    <note/>
  </book>
</library>
Часть В примере Что это
XML-декларация <?xml version="1.0" encoding="UTF-8"?> Необязательная первая строка пролога: версия и кодировка. Если есть — строго первая, без пробелов перед ней
Комментарий <!-- Каталог ... --> Пояснение для человека, парсер его пропускает. Внутри нельзя писать -- и вкладывать комментарии
Корневой элемент <library> Единственный элемент верхнего уровня, всё остальное вложено в него
Элемент <book>, <title> Открывающий тег, содержимое, закрывающий тег. Элементы образуют дерево
Атрибут id="1", lang="ru" Пара имя-значение в открывающем теге, значение всегда в кавычках
Текст Основы XML, 2026 Содержимое элемента. Для XML это всегда строка: 2026 станет числом только в программе
Пустой элемент <note/> Сокращение для <note></note>

Строку с версией и кодировкой правильно называть XML-декларацией. Пролог шире: помимо декларации в него входят комментарии, инструкции обработки и объявление типа документа (DOCTYPE) до корневого элемента.

Правила корректного (well-formed) документа

Документ называют well-formed, если он соблюдает синтаксис XML. В отличие от браузера с HTML, XML-парсер ошибку не прощает, а останавливается:

Ошибка Почему Как правильно
<a/><b/> на верхнем уровне Корневой элемент ровно один <root><a/><b/></root>
<item>текст Каждый открытый тег закрывается <item>текст</item> или <item/>
<a><b></a></b> Элементы вкладываются целиком, не пересекаются <a><b></b></a>
<Title>...</title> Регистр значим: Title и title — разные имена <title>...</title>
<book id=1> Значение атрибута всегда в кавычках <book id="1">
<cond>a < b</cond>, Tom & Jerry < начинает тег, & начинает сущность a &lt; b, Tom &amp; Jerry
<data>a]]>b</data> Последовательность ]]> в тексте запрещена (она закрывает секцию CDATA) <data>a]]&gt;b</data>

Кроме &lt; и &amp; есть &gt;, &quot; и &apos;. В обычном тексте обязательно экранировать < и &. Отдельный запрет спецификации: последовательность ]]> в тексте недопустима (она закрывает секцию CDATA), поэтому > в такой позиции пишут как &gt;.

Well-formed — только про синтаксис. Что в <year> должно стоять число, а id обязателен, XML сам не проверяет — для этого есть схема.

Элемент или атрибут: когда что

<book id="1"> и <book><id>1</id> одинаково корректны. Выбор — по признакам:

  • Атрибут — короткое значение, которое описывает элемент: идентификатор, язык, единица измерения. Не повторяется в теге и не содержит вложенной структуры.
  • Элемент — значение может повторяться (несколько авторов), состоит из частей (адрес), длинное или усложнится в будущем.

Сомневаетесь — делайте элемент, его проще расширить. В форматах с готовой схемой (SVG, Android, sitemap) выбор уже сделан за вас.

Пространства имён

Одноимённые элементы из разных словарей в одном документе (<title> книги и <title> из SVG-иконки внутри) различают пространства имён: атрибут xmlns привязывает префикс к уникальному URI.

<catalog xmlns:svg="http://www.w3.org/2000/svg">
  <title>Каталог</title>
  <svg:svg width="10" height="10"><svg:title>Иконка</svg:title></svg:svg>
</catalog>

URI — просто идентификатор, парсер по нему в интернет не ходит. xmlns="..." без префикса задаёт пространство по умолчанию для элемента и потомков — так устроены SVG и sitemap.xml.

Схемы: DTD и XSD

Схема описывает, какие элементы и атрибуты допустимы, в каком порядке и какого типа. Прошедший проверку документ называют валидным — это ступень выше well-formed. Схема нужна там, где XML приходит извне: интеграции, госсистемы, форматы документов.

  • DTD — механизм из спецификации XML 1.0: задаёт состав элементов, но почти не умеет типы данных. Встречается в старых форматах.
  • XSD (XML Schema) — рекомендация W3C, сама является XML-документом, поддерживает типы (xs:integer, xs:date), ограничения и обязательность. Сегодня это основной вариант.

Схема для элемента book из примера: внутри сначала строка title, затем целое year, необязательный note; атрибут id обязателен и тоже целый.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="library">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="book" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="title" type="xs:string"/>
              <xs:element name="year" type="xs:integer"/>
              <xs:element name="note" minOccurs="0"/>
            </xs:sequence>
            <xs:attribute name="id" type="xs:integer" use="required"/>
            <xs:attribute name="lang" type="xs:string"/>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Стандартный xml.etree.ElementTree по XSD не проверяет — нужен пакет lxml (pip install lxml) или утилита xmllint из libxml2. Пример на lxml: валидный документ проходит без исключения, а <year>две тысячи</year> (well-formed, но не по схеме) даёт ошибку валидации:

from lxml import etree

schema = etree.XMLSchema(etree.parse("library.xsd"))
parser = etree.XMLParser(schema=schema)

etree.parse("library.xml", parser)          # валиден: исключения нет
etree.parse("library-bad.xml", parser)
# lxml.etree.XMLSyntaxError: Element 'year': 'две тысячи' is not a valid value
# of the atomic type 'xs:integer'., line 4

Без такого запуска валидация остаётся теорией: файл с текстом в <year> синтаксически корректен, и ловит его именно схема.

XML, JSON и YAML: что выбрать

Критерий XML JSON YAML
Типы данных Всё — строки, типы через схему Число, строка, логическое, null Как JSON, тип угадывается по записи
Комментарии Есть Нет Есть
Проверка структуры DTD, XSD JSON Schema JSON Schema
Объём текста Самый большой Компактный Компактный, чувствителен к отступам
Разбор в Python xml.etree (стандартная) json (стандартная) PyYAML (сторонняя)
Где типично Android, Maven, .NET, SOAP, Office, SVG Web-API Docker Compose, Kubernetes, CI

Формат задан партнёром, инструментом или стандартом — используйте его. Выбираете сами: API и обмен между программами — JSON; конфигурации, которые правят руками, — YAML; XML — при строгой проверке по схеме, смешанном тексте с разметкой или совместимости с XML-экосистемой.

Работа с XML в Python: ElementTree

Модуль xml.etree.ElementTree входит в стандартную библиотеку Python. Разберём документ из примера (строка data):

import xml.etree.ElementTree as ET

root = ET.fromstring(data)          # из файла: ET.parse("library.xml").getroot()

for book in root.findall("book"):
    print(book.get("id"), book.find("title").text, book.findtext("year"))
# 1 Основы XML 2026
# 2 XML in Practice 2024

get() читает атрибут, find() — первый вложенный элемент, findtext() — его текст. Год вернулся строкой '2024', а не числом: int() остаётся на вас.

Если элемента нет, find() возвращает None (не пустой элемент), и обращение к .text у None даст AttributeError. Поэтому результат проверяют:

book = root.find("book[@id='99']")   # такой книги в документе нет
if book is None:
    print("книга не найдена")        # find() вернул None
else:
    print(book.findtext("title"))
# книга не найдена

findall() понимает подмножество XPath — языка выражений для навигации по дереву. Заголовки всех английских книг на любой глубине:

for title in root.findall(".//book[@lang='en']/title"):
    print(title.text)               # XML in Practice

Полный цикл: прочитать, изменить, сохранить, прочитать снова и проверить запись:

# 1. Прочитать
tree = ET.parse("library.xml")
root = tree.getroot()

# 2. Изменить: поднять год у книги id="1"
root.find("book[@id='1']/year").text = "2027"

# 3. Сохранить
tree.write("library.xml", encoding="utf-8", xml_declaration=True)

# 4. Прочитать снова и 5. проверить результат
check = ET.parse("library.xml").getroot()
print(check.find("book[@id='1']/year").text)   # 2027

Создать документ с нуля:

root = ET.Element("library")
book = ET.SubElement(root, "book", id="3")
ET.SubElement(book, "title").text = "Новая книга"
tree = ET.ElementTree(root)
ET.indent(tree)                     # отступы, Python 3.9+
tree.write("catalog.xml", encoding="utf-8", xml_declaration=True)

# catalog.xml:
# <?xml version='1.0' encoding='utf-8'?>
# <library>
#   <book id="3">
#     <title>Новая книга</title>
#   </book>
# </library>

Спецсимволы библиотека экранирует сама. Если у документа есть пространство имён по умолчанию, имена при разборе выглядят как {URI}имя: find("item") вернёт None, а find("{http://example.com/ns}item") — элемент.

Типовые ошибки

Незакрытый или перепутанный тег — «частичного» результата нет:

ET.fromstring("<a><b></a>")
# xml.etree.ElementTree.ParseError: mismatched tag: line 1, column 8

Исправление: "<a><b></b></a>".

Неэкранированный амперсанд — типично при склейке XML из строк вручную:

ET.fromstring("<a>Tom & Jerry</a>")
# xml.etree.ElementTree.ParseError: not well-formed (invalid token): line 1, column 8

Исправление: "<a>Tom &amp; Jerry</a>", а лучше строить дерево через Element/SubElement — экранирование сделает библиотека.

Безопасность разбора чужого XML

Опасность создаёт не сам разбор, а конкретные конструкции во входном документе:

  • Внешние сущности (XXE) — документ через <!DOCTYPE> объявляет сущность со ссылкой на файл или адрес (file:///etc/passwd, внутренний URL сети). Если парсер её подставляет, он может выдать содержимое локального файла или обратиться к чужому серверу.
  • Раскрытие сущностей (billion laughs) — вложенные сущности при подстановке раздуваются до гигабайтов и исчерпывают память.

Стандартный ElementTree внешние сущности по умолчанию не загружает: ссылка на необъявленную сущность прерывает разбор.

xxe = """<?xml version="1.0"?>
<!DOCTYPE root [<!ENTITY x SYSTEM "file:///etc/passwd">]>
<root>&x;</root>"""
ET.fromstring(xxe)
# xml.etree.ElementTree.ParseError: undefined entity &x;: line 3, column 6

Эта ошибка — иллюстрация поведения одного парсера в одной конфигурации, а не доказательство, что любой XML безопасен: другие библиотеки и настройки ведут себя иначе. Поэтому XML от пользователей и внешних систем разбирают пакетом defusedxml (pip install defusedxml): интерфейс тот же (defusedxml.ElementTree.fromstring(data)), опасные конструкции отключены. Риски и меры для недоверенных данных официальная документация Python собирает в разделе XML security модуля xml.

Выводы

  • XML (eXtensible Markup Language) — текстовый формат разметки для хранения и обмена данными; имена тегов задаёт автор документа.
  • XML-файл — обычный текстовый файл (.xml); открывается редактором или браузером, а программы читают его парсером.
  • Документ состоит из необязательной XML-декларации (версия и кодировка), одного корневого элемента, вложенных элементов с текстом, атрибутов в кавычках и комментариев; всё содержимое для XML — строки.
  • Well-formed документ имеет один корень, закрытые и не пересекающиеся теги, атрибуты в кавычках и экранированные <, & и ]]>; состав и типы проверяют схемы DTD и XSD (запуск проверки — xmllint или lxml).
  • JSON компактнее и подходит для API, YAML — для конфигураций, а XML выбирают при строгой проверке по схеме, смешанном тексте с разметкой или совместимости с XML-экосистемой (Android, Maven, .NET, SOAP, Office, SVG, sitemap).
  • В Python XML разбирают модулем xml.etree.ElementTree, а данные от пользователей и внешних систем — пакетом defusedxml с отключёнными опасными конструкциями.

Где XML используется сегодня

Для новых web-API обычно берут JSON, но XML остаётся там, где важны схемы, документы и совместимость:

  • конфигурации и сборка: AndroidManifest.xml и разметка экранов в Android, pom.xml в Maven, .csproj и конфиги в .NET;
  • обмен между системами: SOAP-сервисы (описание в WSDL), электронная отчётность и документооборот с госсистемами, обмен 1С с сайтами (CommerceML);
  • ленты и графика: RSS и Atom, векторный SVG;
  • документы: DOCX, XLSX и PPTX — zip-архивы с XML-файлами внутри;
  • SEO: sitemap.xml — карта сайта для поисковых систем.

Освойте тему на практике

На практике XML встречается как часть задачи: разобрать ответ SOAP-сервиса, поправить манифест Android или собрать sitemap. Основ из статьи хватает, чтобы читать такие файлы и не ломать их; дальше всё зависит от языка и платформы — это уровень программ из каталога курсов по программированию Otus. Как преподаватели разбирают практические задачи, видно на бесплатных открытых уроках.

Смежные темы: HTML и его основные элементы, Кодирование символов и UTF.

FAQ

Чем XML отличается от HTML? HTML — фиксированный набор тегов для отображения страниц, браузер прощает в нём ошибки. В XML теги задаёт автор, а нарушение синтаксиса — ошибка разбора. XHTML — это HTML, записанный по правилам XML.

Как проверить XML-файл на ошибки без программирования? Открыть в браузере: при нарушении синтаксиса он покажет строку и позицию. Проверку по XSD делают онлайн-валидаторы и IDE с расширением для XML.

Что такое XSLT и нужно ли его учить? Язык преобразования XML в другой XML, HTML или текст по шаблонам. Нужен в узких задачах (отчёты, старые интеграции); для типовой работы достаточно парсера и XPath.

OTUS Журнал