<XML>

По всей видимости наиболее точный и честный ответ на вопрос «за что же так с нами поступает XML» - «исторически сложилось».
Судьба XML в чём-то похожа на MPLS - оба были созданы для одной задачи, а популярность снискали в другой.
XML намеревался стать метаязыком для создания языков разметки документов. Но очень быстро его адаптировали под формат сериализации данных при передаче. И к моменту, когда Juniper выбирал формат, в котором API будет принимать запросы, XML стал уже проверенным, зрелым кандидатом.
Сегодня, вероятно, победил бы JSON, но тогда он только начинал свой путь к славе.
https://fs.linkmeup.ru/images/adsm/5/xml_json.png
YAML и protobuf тогда ещё не существовали. Ну и вообще YAML подходит лучше для описания конфигураций, которые редактируются руками, нежели как формат обмена данными.
Прелюбопытная историческая справка по XML, JSON и YAML: YAML: The Missing Battery in Python.

В общем выбор в те дни был предопределён - XML был сверхсовременным и суперудобным,

Сложность читаемости XML компенсируется простотой его программной обработки. Чёткая иерархическая структура, понятные начало, конец и значение. В том же питоне xmltodict изящно любой валидный XML разворачивает в словарь. А вообще вот годная статья про то, как предполагается работать с XML средствами стандартной библиотеки.

Но давайте разбираться с тем, что же в себе интересного таит XML.
У меня нет задачи подвергнуть читателя пыткам и мучительной смерти через зачитывание стандартов, поэтому сильно глубоко мы погружаться не будем, но какую-то скучную базу дать придётся.
XML сам по себе не делает ничего - это только формат представления информации, в отличие от HTML, который как раз таки призван отрисовать содержимое.
XML описывает что за данные внутри, а его теги не определены заранее, опять же в отличие от HTML.
То есть это два брата, похожих друг на друга внешне, но очень разных внутри.

Давайте сначала на отвлечённом примере поразбираемся?

<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book>
    <title>Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
    <instock>
    </instock>
  </book>
  <book>
    <title>Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
    <instock />
  </book>
</bookstore>

Тут у нас XML, описывающий книжный магазин и имеющиеся в нём книги. У каждой книги есть свой набор атрибутов - название, автор, год выпуска, наличие в магазине.

Всё начинается с

XML Prolog

<?xml version="1.0" encoding="UTF-8"?>
Он опционален, однако обычно присутствует и должен идти первой строкой. Версия всегда строго 1.0, кодировка по умолчанию - UTF-8.
Коль скоро он опциональный, далее мы его опускаем.

Дерево элементов

XML представляет из себя дерево, состоящее из отдельных элементов. Оно может быть произвольной вложенности.
Самый первый элемент называется корневым - root, все последующие - его дети.
В примере выше это <bookstore>. Элемент представляет из себя открывающий и закрывающий теги и содержимое.
Теги заключены в угловые скобки и чувствительны к регистру. <bookstore> и <Bookstore> - это разные теги.
Соответственно между каждой парой определены отношения - родитель-ребёнок или собратья (siblings).
Детьми корневого элемента являются элементы <book>. Разные элементы <book> друг для друга являются собратьями.
Как такового понятия списка в XML нет, но по имени элемента мы (и код) понимаем, что они представляют из себя именно список.
У элемента <book> есть дочерние элементы. Их состав совсем не обязательно должен быть одинаковым - XML этого не требует, однако этого может (и скорее всего будет) требовать приложение.

Главное правило XML - каждый открывшийся тег должен быть закрыт: сказал <a> - говори и </a>. Элемент может быть пустым, просто выражая факт своего существования, тогда запись <instock></instock> можно заменить на просто <instock/>.

Атрибуты

Взглянем на другой пример:

<bookstore>
  <book category="cooking">
    <title lang="en">Everyday Italian</title>
    <author>Giada De Laurentiis</author>
    <year>2005</year>
  </book>
  <book category="children">
    <title lang="en">Harry Potter</title>
    <author>J K. Rowling</author>
    <year>2005</year>
  </book>
</bookstore>
Теперь внутри тега появилась строка вида category="cooking". Она описывает дополнительные данные об элементе. Своего рода метаданные.
При этом вот эти две записи абсолютно равноправны с точки зрения XML:
<book category="cooking">
  <title lang="en">Everyday Italian</title>
  <author>Giada De Laurentiis</author>
  <year>2005</year>
</book>

и

<book>
  <category>cooking</category>
  <title>Everyday Italian
    <lang>en</lang>
  </title>
  <author>Giada De Laurentiis</author>
  <year>2005</year>
</book>
То есть XML в терминах ни синтаксиса, ни семантики понятия дочерний элемент и атрибут не разделяет. Это остаётся исключительно решением составителя/разработчика.
В целом к этому следует относиться именно как к метаданным - информации об информации. То есть если это не является неотъемлемым свойством объекта или нужно в служебных целях, то его можно вынести в атрибуты.

Чтобы далеко не уходить, вот пример из netconf:

<rpc message-id=”101″>
<get-config>
  <source>
    <running/>
  </source>
</get-config>
</rpc>
Здесь message-id - это атрибут элемента RPC, который не имеет непосредственного отношения к передаваемым далее данным, но позволяет отследить по message-id ответ сервера (он вставит его в <rpc-reply>).
Ещё один пример, который мы будем разбирать дальше: <interfaces operation="replace">. Атрибут operation="replace" не является частью конфигурации интерфейса, он лишь говорит, что то, что существует сейчас на коробке в ветке <interfaces>, нужно заменить на то, что описано в данном XML.
Ну и замечу, что пусть с точки зрения XML атрибут и дочерний элемент взаимозаменяемы, когда вы придумываете свою схему обмена или хранения, однако NETCONF вам такого не простит. Да и любой другой интерфейс, в который вы встраиваетесь - ведь в нём уже определена схема XML.

Namespaces

Хух. Я откладывал много лет момент, когда придётся разобраться с неймспейсами в XML.
На самом деле ничего тут нет хитрого.
Если мы определили два разных элемента с одинаковыми именами, то появляется неоднозначность - какой именно элемент мы имеем в виду, обращаясь к нему по имени?
Например, элемент <name> может быть как у интерфейса, так и у пользователя и у влана итд. Их можно разнести в разные NS, хотя это не обязательно, потому что они находятся под разными родителями.
А если на одном уровне могут оказаться совпадающие имена - это уже настоящая проблема. Например,
<root>
<address>
  <city>
    <name>Moscow</name>
    <street>Novocheremushkinskaya, 50</street>
  </city>
</address>
<address>
  <ipv6>2a01:ba80:e:20::32</ipv6>
  <ipv4>185.127.149.137</ipv4>
</address>
</root>
В первом случае имеется в виду почтовый адрес, во втором - IP.
Здесь уже однозначно будет конфликт. Надо решать.
Сделать это можно несколькими способами.
  1. Прямо объявляем неймспейсы с префиксами:

    <root>
    <postal:address xmlns:postal="https://www.linkmeup.ru/postal_address/">
      <postal:city>
        <postal:name>Moscow</postal:name>
        <postal:street>Novocheremushkinskaya, 50</postal:street>
      </postal:city>
    </postal:address>
    <ip:address xmlns:ip="https://www.linkmeup.ru/ip/">
      <ip:ipv6>2a01:ba80:e:20::32</ip:ipv6>
      <ip:ipv4>185.127.149.137</ip:ipv4>
    </ip:address>
    </root>
    

    Теперь это полное, fully qualified, имя безо всяких ограничений. Обращаемся из приложений, соответственно, по полному имени. postal и ip - это короткие префиксы. Само имя namespace - это произвольная строка. Но негласная договорённость, что все используют URI. Он может вести на страницу с описанием этого неймспейса, а может и не вести. Но указание префикса в каждом теге может показаться не очень удобным, тогда есть второй способ.

  2. Определяем default namespace

    <root>
    <address xmlns="https://www.linkmeup.ru/postal_address/">
      <city>
        <name>Moscow</name>
        <street>Novocheremushkinskaya, 50</street>
      </city>
    </address>
    <address xmlns="https://www.linkmeup.ru/ip/">
      <ipv6>2a01:ba80:e:20::32</ipv6>
      <ipv4>185.127.149.137</ipv4>
    </address>
    </root>
    

Область действия дефолтного неймспейса - сам элемент и все его потомки, если он нигде не переопределяется.

Концепция namespace с одной стороны проста, с другой стороны и там есть место тёмным пятнам. Если хочется подетальнее изучить, то есть пара полезных FAQ про них.

Xpath - XML Path

Сначала правильно, но непонятно: XPath - это способ выбрать ноды или множество нод из XML документа.
Теперь неправильно, но понятно: это способ представить иерархию XML в виде «привычного» нам пути, где элементы отделены друг от друга знаком «/».

Например, в XML из примера выше путь к элементу <title> будет записан в виде /bookstore/book/title

Ну а теперь и правильно, и понятно, но долго.
XPath - это очень гибкий и мощный инструмент, позволяющий внутри XML делать разнообразные запросы. Он поддерживает различные функции: sum, count, avg, min, starts-with, contains, concat, true, false - над разными типами данных: числа, строки, булевы.
Так с помощью XPath можно выбрать названия всех книг с ценою выше 35: /bookstore/book[price>35]/title

XPath оперирует нодами, которыми являются элементы, атрибуты, текст, неймспейсы и другое.

Соответственно помимо того, что мы можем запросить часть XML по конкретному пути, можно делать разные хитрые запросы.
Например:
  • Вернуть BGP-группу, в которой есть peer 10.1.1.1
  • Вернуть интерфейс, на котором число ошибок больше 100
  • Вернуть список интерфейсов, на которых native-vlan 127
  • Вернуть количество интерфейсов, в имени которых есть «Ethernet».

В контексте NETCONF вы можете его встретить, но это не самая популярная capability. В общем, знать про него полезно, но глубоко копать не будем. Если хочется поподробнее почитать, то это можно сделать например, тут.

Схема

Что такое XML - это удобный способ передавать структурированные данные между приложениями. Но это лишено какого-либо смысла, есть нет контракта о том, как данные в этих файлах должны храниться - где какие элементы и какого они типа.
Представьте, что информацию об IP-адресах мы будем помещать непосредственно в элемент <interface>, а читать его пытаются из элемента <unit>?
Или дату мы передаём в формате YYYY-MM-DD, а читать её пытаются в MM-DD-YYYY (больные ублюдки).
При этом сам XML будет абсолютно «Well Formed», что называется, - то есть соответствовать синтаксису XML.
Для этого и существует Схема. В отдельном XML-файле описывается схема данных для основного XML.
Это позволяет
  • двум сторонам использовать один и тот же способ хранения и распаковки данных.
  • описывать содержимое документа
  • определять ограничения на данные
  • проверять корректность XML

Называется это хозяйство XML Schema Definition - или коротко XSD.

Поскольку это тот же самый XML, он должен как-то обозначать себя, что является схемой. Для этого есть ключевой элемент <schema>. Вот так будет выглядеть XSD для кусочка XML выше:

<xs:schemaxmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:elementname="address">
    <xs:complexType>
      <xs:sequence>
        <xs:elementname="country_name" type="xs:string"/>
        <xs:elementname="population" type="xs:decimal"/>
      </xs:sequence>
    </xs:complexType>
</xs:element>
</xs:schema>

При этом в самом XML можно дать ссылку на XSD

<note
xmlns="https://www.linkmeup.ru"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.linkmeup.ru/404.xsd">

Самостоятельное продолжение изучения XSD.

Лучшая сторона XSD - это то, что на его основе можно автоматически генерировать объекты в языках программирования. То есть XSD описывает, какие именно объекты и структуры должны быть созданы, а конкретный XML - наполняет экземпляр, пользоваться которым значительно удобнее, чем крафтить XML. Со схемами и моделями мы будем разбираться дальше.

Надеюсь получилось, не утопая в деталях, дать понимание, что из себя представляет XML. Далее для нас это будет важным.

</XML>