XML — как это работает

Я поначалу совсем не хотел останавливаться на том, что такое XML, но все-таки несколько слов сказать придется, чтобы те, кто про него услышали первый раз, прочитав эти строки, были в курсе основных идей.
По сути XML представляет собой обычный текст, который разделяется на логические группы с помощью специальных меток, которые называют “тэг”.
Тэг представляет собой слово, которое заключено в угловые скобки — например вот так:

<test>

Для того, чтобы начать группу вы указывает просто слово в скобках — еще раз повторим <test>
Для окончание группы вы указываете такой же тэг, но слово предваряется символом “/”. Вот так: </test>

В итоге группа внутри тэга test выглядит вот так:
<test>Группа символов для тестирования</test>

Тэги могу вкладываться один в другой — например вот так:
<testGroup>
<test>Группа символов для тестирования 01</test>
<test>Группа символов для тестирования 02</test>
<test>Группа символов для тестирования 03</test>
</testGroup>

Кроме вложений текста в тэгах можно указывать атрибуты — вот так:

<test attribute=”1” >Группа символов для тестирования 01</test>

Назначение тэгов очень простое — надо отметить/выделить/сгруппировать какую-то информацию для того, чтобы потом ее можно было использовать. Это может быть список имен, список книг, список фирм, список вакансий и т.д.
Например, я хочу написать список контактов, с указанием имени, фамилии и e-mail. Можно сделать это так (но можно и по-другому — здесь все зависит от вашей фантазии и требований задачи):

<contacts>
<contact type=”друг”>
<firstName>Василий</firstName>
<lastName>Курочкин</lastName>
<email>vas@pisem.net</email>
</contact>
<contact type=”коллега”>
<firstName>Георгий</firstName>
<lastName>Папанов</lastName>
<email>geogr@gmail.com</email>
</contact>
<contact type=”однокурсник”>
<firstName>Семен</firstName>
<lastName>Баринов</lastName>
<email>barinov@yandex.ru</email>
</contact>
</contacts>

Не ищите тайного смысла — я просто сделал строку, в которой выделил нужные мне части — контакт (тэг contact) и внутри определил имя, фамилию и e-mail (тэги firstName, lastName, email). Также с помощью атрибута type я определи тип контакта — друг, коллега, однокурсник. Теперь просматривая строку я могу выделить нужные мне части информации. Это удобно и ничего более. Причем здесь больше удобства даже не для визуального восприятия (это спорно), а для программной обработки — достаточно несложно написать программу, которая найдет конкретные кусочки.

Теперь новичкам надо посмотреть какой-нибудь XML, чтобы увидеть больше примеров и убедиться, что в главном я прав 🙂 (хотя все в мире относительно).

Работа с XML

В первую очередь я хотел бы высказать свою позицию по поводу самого XML и уже на основе этого продолжать повествование.
Для меня XML — очень мощная технология, которая позволяет хранить, передавать и обрабатывать сложноструктурированные данные. Т.е. если я хочу иметь: список фирм с их телефонами и счетами, каталог книг с авторами и отзывами, описание структуры страниц сайта с комментариями, состояние всех автобусов в городе с их координатами, водителями, номерами и прочая — все это может быть удобно сохранено в виде XML и, что крайне важно и удобно, может быть передано в любую систему, которая написана на любой платформе — на .NET, PHP, Object C, Delphi, C++.
Проведите мысленный эксперимент — попробуйте написать строку, в которой передать информацию о своих контактах (где у одной персоны может быть несколько телефонов, e-mail, любимые книги, места работы, места учебы и … да хватит пока). Что важно — это должна быть обычная строка (несколько строк), которая позволяет разбирать эту информацию в ту структуру, которую я описал — класс Java. Там надо предусмотреть какие-то разделители, информацию об именах полей (группах полей). Попробуйте — и вы придете к чему-то подобному XML.

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

Итак, после всего вышесказанного мы видим, что нам приходит строковое представление чего-то важного в какой-то определенной структуре, которая требует наличия достаточно важных функций — нам же надо как-то работать с этой информацией. Не лежать же ей мертвым грузом. Функции достаточно очевидны:

  1. Разбор. Надо уметь разобрать строку на что-то более удобное для обработки — пытаться вставить внутрь строки или находить какое-то поле определенной записи из строки — это достаточно неудобно. Значит нам надо иметь некоторый набор классов для представления нашей строки в виде структуры объектов.
  2. Поиск. По структуре данных надо уметь что-то искать. Причем не подстроку, а какую-то группу полей, которые относятся к определенному объекту — например полная информация о книге — наименование, авторы, отзывы. Или список контактов с фамилией “Сидоров”.
  3. Проверка. Данные должны быть корректными, т.е. там должны быть только определенные поля, с определенным наполнением и они должны быть правильно скомпанованы в нашей строке.
  4. Преобразование. Хоть XML достаточно удобно описывает структурированные данные, это не значит, что его удобно просматривать обычному человеку или всегда удобно обрабатывать. Нередко для решения этого вопроса требуется преобразовать XML в какое-либо другое текстовое представление — например в тот же HTML (который является частным воплощением XML). Или даже в обычный текст.

В общем-то это все, что на мой взгляд наиболее важно. Нам надо уметь работать с информацией, которая записана в формате XML. Приступим к рассмотрению каждого пункта.

Разбор — Parsing

В слэнге программистов часто используется слова “парсинг”, который и обозначает разбор строки (или еще чего-нибудь) в какую-то структуру. Здесь надо выделить два момента:

  1. Разбор строки определенным алгоритмом
  2. Сохранение результатов разбора в какую-то структуру вместо строки — ибо со строкой многие операции делать просто неудобно.

Что касается второго пункта, то на сегодня существует по сути одна унифицированная структура, которая называется Document Object Model (DOM).
DOM представляет собой набор интерфейсов (и их реализаций), которые являются специализированными объектами для хранения “узлов” (node) XML-документа. По сути каждый тэг — это “узел” (нода — я буду использовать этот термин, т.к. очень привык). Информация внутри тэга — тоже нода. По сути любой разобранный XML — это набор элементов типа Node и еще более специализированных, построенных в дерево.
Почему дерево ? Да потому что у каждой ноды может быть список дочерних нод и у каждой из них тоже может быть “детки”. Так и строится дерево. Если вам сложно это увидеть, то советую посмотреть какую-либо классическую книгу по алгоритмам и структурам данных — можно старого доброго Никлауса Вирта «Алгоритмы и структуры данных». Книгу можно найти в интернете, ну или купить.

DOM

Давайте рассмотрим загрузку файла и редактирование построенного дерева на примере. Наша программа считывает XML-файл со списком книг и печатает их свойства. Сам XML-файл выглядит вот так:

Если вы будете создавать проект в NetBeans (или в Idea), то XML-файл надо положить в папку с проектом — в самый корень.
Теперь сама программа для разбора и печати:

Ну что же — давайте разбираться, что и как тут работает. Для того, чтобы загрузить структуру мы должны создать объект класса DocumentBuilder (строка 17).
Потом загружаем текстовый XML-файл и разбираем его, получая объект Document (строка 19). Это и есть объектное представление всей информации внутри XML — наше дерево (сразу вспоминаются «Джентльмены удачи» — «а вон оно, дерево»).
Дальше мы начинаем «обход» нашего дерева, используя методы, которые предоставляют стандартные объекты DOM — Node, NodeList. Для того, чтобы использовать эти объекты, их честно надо изучать — смотреть, какие у них есть методы и т.д. Я предлагаю посмотреть код программы и комментарии — некоторые особенности работы с DOM вам станут понятны из кода. Если кородко — класс Node предназначен для любого элемента XML — текст или тэг или атрибут. Т.е. все в XML есть Node. Дальше идет специализация за счет наследования.
Самое главное преимущество DOM (на мой взгляд) заключается в возможности редактировать данные — ведь по сути это деревообразная структура, состоящая из унифицированных объектов, она загружена полностью в память и мы можем туда добавлять новые элементы, исправлять или удалять существующие. Также можно осуществлять поиск и делать выборку. Это дает хорошие возможности для решения конкретных задач при работе с данными. Предлагаю пример, в котором мы считаем наш XML-файл с книгами и добавим еще одну книгу в нашу структуру. После этого мы сохраним XML в файл. Самым важным тут будет метод addNewBook — именно в нем мы создаем унифицированные объекты, которые потом вставляются в наше дерево. Также мы используем наследника класса Node — класс Element. Этот класс предназначен именно для тэгов. Ему можно устанавливать имя и атрибуты.

Я прекрасно понимаю, что представил не очень много информации для полного понимания и большой практики работы с XML в виде DOM, но думаю, что начало положено и вы сможете сами пойти дальше. Еще раз повторюсь — на самом деле вы просто построили дерево объектов, каждый из которых является отдельным элементом XML-документа и у вас есть инструмент, чтобы его редактировать.
Попробуйте добавить иную структуру в наше дерево — например список контактов с полями Имя, Фамилия, телефон и email. Вся необходимая информация у вас уже есть.

Сама модель DOM — это конечный результат разбора XML-строки (из файла или из какого-то другого источника — например из сети по URL). Но ведь кто-то разобрал эту строку — и самое время узнать про SAX — Simple API for XML.

SAX

SAX — это набор классов и интерфейсов, задача которых дать механизм разбора XML в строковом представлении. Основными классами я бы назвал два:

  1. SAXParser
  2. DefaultHandler

Я бы так описал их совместную работу: класс SAXParser начинает читать содержимое XML и когда наступает момент «Х» — начинается новый тэг, заканчивается тэг, начинается текст внутри тэга — он может вызывать определенную функцию класса DefaultHandler. Для того, чтобы «наполнить» эти вызовы со стороны SAXParser хоть каким-то смыслом, надо расширить класс DefaultHandler.
Давайте посмотрим пример печати содержимого тэга NAME из XML такого вида:

Сама программа выглядит так:

Как видите, все достаточно несложно — но это на первый взгляд. Если у вас достаточно запутанный XML, то его разбор может стать весьма сложной задачей — попробуйте вложить внутрь тэга NAME еще какой-нибудь тэг с вложенным текстом перед именем Joe Wang. Что-то вроде такого (я привожу только кусочек, чтобы сразу было видно, что мы сделали:

Как вы можете догадаться (лучше конечно же убедиться, запустив программу) вместо Joe Wang будет напечатано Noname person. Что делать и как это надо исправлять — ваша домашняя задача. Как я уже упоминал — алгоритмически это может быть не так просто.

Но тем не менее почему SAX используется ? Самое главное это то, что он позволяет читать просто гигантские файлы — если вам надо при обработке только что-то найти и изредка что-то запомнить. Ведь созданием каких-либо объектов для получения результатов парсинга управляете вы сами. С одной стороны — все в ваших руках. С другой стороны — ответственность тоже на вас. Что «насобираете», то и ваше.

В моей практике наиболее часто встречалась задача импорта больших объемов данных. В одной системе подготавливается огромный XML (сотни мегабайт) и он передается в другую систему, где данные из него можно загрузить в базу. Например список фирм или транзакций по каким-то операциям. Информация об одной фирме занимает мало места и ее легко загрузить в память и потом записать в базу, но количество таких записей может выражаться сотнями миллионов. В такой ситуации SAX решает задачу очень эффективно и удобно.

StAX

В версии Java SE 6 появился еще один вариант разбора XML — Streaming API for XML (StAX). У этой технологии я хотел бы выделить два важных момента:

  1. В отличии от SAX, который вызывает ваш обработчик тогда, когда считает нужным, в StAX вы сами управляете перемещением по тэгам
  2. StAX позволяет не только читать, но и писать

Давайте не будем сильно заниматься копанием в особенностях StAX, а посмотрим приер, который иллюстрирует базовые основы работы StAX при чтнеии файла.

Как видите, все достаточно просто — вы создаете объект класса XMLStreamReader, который позволяет вам перемещаться последовательно по всем элементам XML и проверять, что за элемент вы нашли. У класса XMLStreamReader есть ряд методов, которые позволяют получить доступ к свойствам элемента XML — имя, текст, атрибуты. Что с этой информацией делать — опять же зависит от вашей задачи.
Самый главный вопрос — а зачем все это надо ? Ответ такой же, как и для SAX — обработка больших объемов данных.

Теперь пример, как можно использовать StAX для записи файла. Что здесь хорошего — можно писать большие файлы. Что же касается удобства — на мой взгляд не самый удобный способ, хотя в нем есть своя элегантность и целостность. Пример достаточно несложный — он пишет в XML несколько книг. Смотрим.

В общем мы рассмотрели основные варианты разбора XML, которые предоставляет Java. Отмечу еще раз — цель всех этих технологий — разобрать строку на отдельные элементы. Больше ничего. Есть еще одна интересная (и востребованная) технология — JAXB (Java Architecture for XML Binding). Я отложу ее рассмотрение до другого раза. А пока займемся следующим функционалом — поиском по сформированному дереву.

Прежде чем мы начнем рассмотрение этого вопроса, предлагаю вам попробовать написать код для поиска названия книги у которой должен быть определенный автор и цена в рублях (заранее занесите такого в XML). Используйте DOM для построения полного дерева элементов и уже для него вам надо написать код. Это не самое простое занятие, но зато попрактикуетесь.

Поиск по XML — XPath

Для осуществления поиска нужных вам данных был разработан специальный язык — XPath. Я не ставлю себе цель научить вас всем тонкостям этого языка — я предлагаю посмотреть как можно его использовать для того, чтобы найти нужную вам информацию. Самым правильным решением на мой взгляд (как всегда) является пример использования. Вот давайте его и посмотрим. Пример демонстрирует, как можно найти ту или иную книгу (книги) по определенным критениям.

Как легко можно увидеть, в каждой функции для работы с XPath используется одна и та же последовательность действий.

Еще раз проговорю главную цель XPath — надо находить информацию по определенным критериям. Рассматривайте XPath именно с этой позиции и вы получите очень удобный и эффективный инструмент для работы с XML.

Что касается изучения непосредственно XPath, то могу рекомендовать следующие сайты:

Проверка

Когда вы начинаете работать с какой-либо структурой, то важным моментом является ее правильное заполнение. Для XML это включает целый ряд задач — правильное написание самого XML, указание только определенных тэгов, взаимное расположение тэгов (например один тэг может быть только внутри другого, а не наоборот), набор атрибутов, значения полей и атрибутов. Все это весьма важно.
Если мы договариваемся передавать определенную информацию между двумя системами, то они должны заранее знать, какие именно тэги и с какой информацией будут включаться в XML. Постарайтесь осознать важность проверки корректности заполнения информации — ведь в реальную систему нередко приходят миллионы сообщений и отбрасывать некорректные — благородная и ответственная задача. Для этой цели было разработано механизмов, о которых мы вкратце и поговорим.

В первую очередь XML должен быть well-formed — «правильно сформированный». Это значит, что все тэги должны «закрываться» (подобно скобкам в математическом выражении), закрытие и открытие тэгов не должно пересекаться и т.д. Более подробно можно посмотреть анпример здесь: Well-formed document.
Но кроме просто правильно сформированного XML есть потребность более четкого (жесткого) определения структуры XML-документа. И эта потребность имеет свое решение.
В самом начале работы над XML был предложен механизм DTD — Document Type Definition. Это описание структуры XML-документа — какие тэги допустимы, в каком порядке, какой тэг является вложенным и для какого, набор атрибутов и прочая. Для знакомства можно почитать эту статью: Document type definition. На сегодня этот вариант считается устаревшим и на его смену пришла XML-схема — XML Schema.
XML Schema представляет собой файл (чаще всего с расширением XSD — XML Shema Definition), который с форме XML (да-да, именно так — описываем правила формирования XML с помощью XML) описывает что и в каком виде может быть включено в XML-документ. XML Schema является весьма любопытным объектом для изучения и я настоятельно рекомендую почитать об этой технологии. В ней есть очень много моментов, про которые надо почитать отдельно от какого-либо языка. Вот несколько источников:

  • XML Schema Tutorial.
  • «The Art of XSD. SQL Server XML Schema Collections» Jacob Sebastian — мне эта книга очень понравилась

Но цель этой статьи состоит в том, чтобы показать, как можно использовать XSD в Java. Так что предлагаю посмотреть пример, как надо сделать проверку XML по указанному XSD. Сначала приведу XSD и XML для примера.
XSD-файл, который описывает правила формирования XML — здесь мы описали формат сообщения, которое предположим может быть послано от гражданина в какой-нибудь департамент (основано на вполне реальном файле).

А это наш XML с корректной информацией (с заполненным сообщением)

Ну и наконец Java-код, который позволяет проверить корректность XML по его XSD.

Теперь можно «поиграть» с нашими данными и посмотреть, как реагирует наша программа на изменения в данных. Я бы рекомендовал попробовать создать свой вариант XML и XSD и использовать нашу программу для их проверки.

Преобразование

Необходимость преобразования XML во что-то другое лежала на поверхности и ее реализовали. Эту функцию решает технология XSLT (Extensible Stylesheet Language Transformations). У меня нет цели в этой статье углубляться в сам XSLT — для этого можно посмотреть в интернете неплохие статьи — например:

Я только остановлюсь на вариантах использования, которые логически вытекают из самого XML.
Как я уже говорил, XML весьма удобен для хранения и передачи информации в структурированном виде. Но для отображения это далеко не самый лучший вариант. Т.е. само преобразование требуется несомненно. Кроме того учитывая, что сами данные в XML могут предназначаться разным системам, надо иметь возможность гибко настраивать такое преобразование для каждой системы. Например, информация о тех же книгах для десктопного компьютера может быть представлена в одном виде, а для мобильного телефона — совсем в другом (более компактном). Информация в одном экземпляре, а ее представление — во многих. Удобно.
Мало того, с учетом все возрастающей мощности клиентских компьютеров/планшетов/смартфонов появляется возможность возложить ношу преобразования на них — передаем им XML и файл для преобразования (XSL-файл) и пусть они сами его преобразовывают. Это может серьезно снизить нагрузку на серверную сторону и тем самым повысить общую производительность.

А теперь посмотрим пример преобразования каталога книг в HTML-таблицу. Здесь важно увидеть, что информация для преобразования может быть в любой момент изменена без изменения кода. Комментарии смотрите в самом коде.
XML с данными о книгах

XSL для преобразования в HTML-таблицу

Ну и сама программа для запуска преобразования.

В качестве домашнего задания — попробуйте сделать систему, которая может выбирать один из нескольких XSL по аргументу, который передается в функцию xmlToString.

И теперь нас ждет следующая статья: Reflection — основы.