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 < b, Tom & Jerry |
<data>a]]>b</data> |
Последовательность ]]> в тексте запрещена (она закрывает секцию CDATA) |
<data>a]]>b</data> |
Кроме < и & есть >, " и '. В обычном тексте обязательно экранировать < и &. Отдельный запрет спецификации: последовательность ]]> в тексте недопустима (она закрывает секцию CDATA), поэтому > в такой позиции пишут как >.
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 & 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.



