Работа с XML. XML-сериализация

1 Теоретическая часть

1.1 Работа с XML-документами

1.1.1 Общие положения

Расширяемый язык разметки XML (eXtensible Markup Language) – это независимый от платформы метод структурирования информации. Поскольку XML отделяет содержание документа от его структуры, его успешно используют для хранения и обмена информацией. Основные применения XML – хранение структурированной информации, представление содержания Web-сайта, передача данных между программой и базами данных, а также между базами данных отличающихся форматов.

Файлы формата XML – это всегда текстовые файлы. Синтаксис языка XML во многом похож на синтаксис языка HTML, который применяется для разметки текстов, публикуемых в Internet. Язык XML также может быть непосредственно применен для разметки текстов.

Документ в формате XML начинается с так называемого префикса. Префикс документа в общем случае имеет такой вид:

<?xml version="1.0" [другие-атрибуты] ?>

Среди возможных атрибутов наиболее полезный атрибут – это encoding="кодирование". Он задает таблицу кодирования текста. Если необходимо использовать однобайтовые (не UNІCODE) символы кириллицы, это можно определить, например, таким образом:

<?xml version="1.0" encoding="Windows-1251"?>

Рекомендуемая для XML-документов кодировка – UTF-8 (эта кодировка принята по умолчанию). В этом случае для текстов, включающих символы национальных алфавитов, проблемы с сохранением и воспроизведением информации будет минимальной. После заголовка может помещаться информация о типе документа. Оставшаяся часть XML-документа состоит из набора XML-элементов, любой из которых содержит начальный и конечный теги – идентификаторы в угловых скобках: <...>. Перед соответствующим конечным тегом располагается символ /. Каждая пара тегов представляет часть данных (элемент XML-документа).

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

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

В следующем примере в XML-файле сохраняются простые числа.

<?xml version="1.0" encoding="UTF-8"?>
    <Numbers>
    <Number>1</Number>
    <Number>2</Number>
    <Number>3</Number>
    <Number>5</Number>
    <Number>7</Number>
    <Number>11</Number>
</Numbers>

Теги Numbers и Number придумал автор документа. Отступы в тексте файла использованы для улучшения его восприятия человеком.

Теги могут включать в себя атрибуты – дополнительную информацию об элементах, которая помещается внутри угловых скобок. Значения атрибутов обязательно берут в кавычки. Например, можно предложить тег Message с атрибутами to и from:

<Message to="you" from="me">
    <Text>
        Для чего нужен XML?
    </Text>
</Message>

Важным правилом формирования XML является обязательность употребления конечных тегов. Кроме того нельзя путать порядок конечных тегов. Этот текст содержит ошибку:

<A> <B> text </A> </B>

Необходимо писать так:

<A> <B> text </B> </A>

Допускается использование пустых тегов. Такие теги не используют для обрамления элементов. Такие теги заканчиваются символом /. Например, можно употреблять <Nothing/> вместо пары <Nothing></Nothing>.

В XML-документе можно использовать комментарии, синтаксис которых совпадает с синтаксисом комментариев HTML-документов:

<!-- Это комментарий -->

Как и HTML, для включения в текстовые данные, хранимые в документе, символов, используемых для разметки, таких как <, >, ", &, используются &lt;, &gt;, &quot; и &amp; соответственно.

1.1.1 Стандартные подходы к работе с XML-документами. Средства JAXP

Существует два стандартных подхода к работе с XML-документами в программах:

  • Событийно-ориентированная модель документа (Simple API for XML, SAX), работающая на потоке данных, обрабатывая его по мере возникновения событий, связанных с различными тегами;
  • Объектная модель документа (Document Object Model, DOM), позволяющая создать в памяти коллекцию узлов, организованных в иерархию.

Событийно-базированный подход не позволяет разработчику изменять данные в исходном документе. В случае необходимости корректировки части данных документ нужно полностью обновить. В отличие от него DOM обеспечивает API, который позволяет разработчику добавлять или удалять узлы в любой точке дерева в приложении.

Оба подхода используют понятие парсера. Парсер (parser) – это программное приложение, которое предназначено для того, чтобы анализировать документ путем разбиения его на лексемы (tokens). Парсер может инициировать события (как в SAX), либо строить в памяти дерево данных.

Для реализации стандартных подходов к работе с XML в Java SE используются средства Java API for XML Processing (JAXP, интерфейс программирования приложений Java для работы с XML). JAXP предоставляет средства валидации и разбора XML-документов. Для реализации объектной модели документа JAXP включает программный интерфейс DOM, SAX реализован одноименным программным интерфейсом. В дополнение к ним предоставлен программный интерфейс Streaming API for XML (StAX, потоковый API для XML), а также средства XSLT (XML Stylesheet Language Transformations, язык преобразования XML-документов).

1.1.2 Использование Simple API for XML и StAX

Simple API for XML (SAX, простой программный интерфейс для работы с XML) предоставляет последовательный механизм анализа XML-документа. Анализатор, реализующий интерфейс SAX (SAX Parser), обрабатывает информацию из XML документа как единый поток данных. Этот поток данных доступен только в одном направлении, то есть, ранее обработанные данные не повторно прочитать без повторного анализа. Большинство программистов сходится во мнении, что обработка XML документов с использованием SAX, в целом, быстрее, чем при использовании DOM. Это объясняется тем, что поток SAX требует гораздо меньшего объема памяти по сравнению с построением полного дерева DOM.

SAX анализаторы реализуют с использованием событийно-управляемого (event-driven) подхода, когда программисту необходимо описать обработчики событий, которые вызываются анализаторами при обработке XML документа.

Средства Java SE для работы с SAX реализованы в пакетах javax.xml.parsers и org.xml.sax, а также во вложенных в них пакетах. Для создания объекта класса javax.xml.parsers.SAXParser необходимо воспользоваться классом javax.xml.parsers.SAXParserFactory, представляющим соответсвующие фабричные методы. SAX парсер не создает в памяти представление документа XML. Вместо этого, SAX парсер информирует клиентов о структуре документа XML, используя механизм обратного вызова. Можно самостоятельно создать класс, реализующий ряд необходимых интерфейсов, в частности org.xml.sax.ContentHandler Однако более простой и рекомендуемый способ - использовать класс org.xml.sax.helpers.DefaultHandler, создав производный класс и перекрыв его методы, которые должны вызваться при возникновении различных событий в процессе анализа документа. Наиболее часто перекрываются такие методы:

  • startDocument() и endDocument() - методы, которые называются в начале и в конце анализа XML-документа
  • startElement() и endElement() - методы, которые называются в начале и в конце анализа элемента документа
  • characters() - метод, вызываемый при получении текстового содержимого элемента XML-документа.

Следующий пример иллюстрирует использование для чтения документа. Допустим, в каталоге проекта создан файл Hello.xml следующего содержания:

<?xml version="1.0" encoding="UTF-8" ?>
<Greetings>
    <Hello Text="Привет, это атрибут!">
        Привет, это текст!
    </Hello>
</Greetings>

Примечание. При сохранении файла следует указать кодировку UTF-8.

Код программы, которая читает данные из XML, будет таким:

package ua.inf.iwanoff.xml;

import java.io.IOException;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class HelloSAX extends DefaultHandler {

    @Override
    public void startDocument() {
        System.out.println("Opening document");
    }

    @Override
    public void endDocument() {
        System.out.println("Done");
    }

    @Override
    public void startElement(String uri, String localName, String qName,
          Attributes attributes) throws SAXException {
        System.out.println("Opening tag: " + qName);
        if (attributes.getLength() > 0) {
            System.out.println("Атрибуты: ");
            for (int i = 0; i < attributes.getLength(); i++) {
                System.out.println("  " + attributes.getQName(i) + ": "
                                    + attributes.getValue(i));
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName)
                                      throws SAXException {
        System.out.println("Closin tag: " + qName);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        String s = new String(ch).substring(start, start + length).trim();
        if (s.length() > 0) {
            System.out.println(s);
        }
    }

    public static void main(String[] args) {
        SAXParser parser = null;
        try {
            parser = SAXParserFactory.newInstance().newSAXParser();
        }
        catch (ParserConfigurationException | SAXException e) {
            e.printStackTrace();
        }
        if (parser != null) {
            InputSource input = new InputSource("Hello.xml");
            try {
                parser.parse(input, new HelloSAX());
            }
            catch (SAXException | IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Поскольку метод characters() вызывается для каждого тега, содержимое есть смысл выводить, если строка не пустая.

StAX был разработан как нечто среднее между интерфейсами DOM и SAX. В данном программном интерфейсе используется метафора курсора, представляющего точку входа в пределах документа. Приложение перемещает курсор вперед, читая информацию, получая информацию от синтаксического анализатора по мере необходимости.

1.1.3 Использование Объектной модели документа

DOM является серией Рекомендаций, вырабатываемых Консорциумом World Wide Web (W3C). DOM начиналась как способ идентификации и манипулирования элементами на HTML-странице (DOM Level 0).

Действующая Рекомендация DOM (DOM Level 3) является API, который определяет объекты, представляемые в XML-документе, а также методы и свойства, которые используются для доступа к ним и манипулирования ими.

Начиная с DOM Уровня 1, DOM API содержит интерфейсы, которые представляют всевозможные типы информации, которые могут быть найдены в XML-документе. Он также включает в себя методы, необходимые для работы с этими объектами. Можно привести некоторые наиболее употребительные методы стандартных интерфейсов DOM.

Интерфейс Node является основным типом данных DOM. Он определяет ряд полезных методов для получения данных об узлах и навигации по ним:

  • getFirstChild() и getLastChild() возвращают первого или последнего потомка данного узла;
  • getNextSibling() и getPreviousSibling() возвращают следующего или предыдущего "брата" данного узла;
  • getChildNodes() возвращает ссылку на список типа NodeList потомков данного узла; с помощью методов интерфейса NodeList можно получить i-й узел (метод item(i)) и общее количество таких узлов (метод getLength());
  • getParentNode() возвращает "родительский" узел;
  • getAttributes() возвращает ассоциативный массив типа NamedNodeMap атрибутов данного узла; getNamedItem()
  • hasChildNodes() возвращает true, если узел имеет потомков.

Существует ряд методов, обеспечивающих модификацию XML-документа – insertBefore(), replaceChild(), removeChild(), appendChild() и т.д.

Кроме Node, в DOM также определяет несколько подинтерфейсов интерфейса Node:

  • Element – представляет элемент XML в исходном документе; в элемент входит пара тегов (открывающийся и закрывающийся) и весь текст между ними;
  • Attr -представляет атрибут элемента;
  • Text – содержимое элемента;
  • Document – представляет весь XML-документ; только один объект типа Document существует для каждого XML-документа; имея объект Document, можно найти корень дерева DOM с помощью метода getDocumentElement(); от корня можно манипулировать всем деревом.

Дополнительными типами узлов являются:

  • Comment – представляет комментарий в XML-файле;
  • ProcessingInstruction – представляет инструкцию обработки
  • CDATASection – представляет раздел CDATA.

XML-парсеры требуют создания экземпляра определенного класса. Недостатком этого является то, что при изменении парсеров нужно изменять исходный код. Для некоторых парсеров иногда можно использовать так называемые фабричные классы. При помощи статического метода newInstance() создается экземпляр "фабричного" объекта, с помощью которого создается объект класса, реализующего интерфейс DocumentBuilder. Такой объект непосредственно является необходимым парсером: реализует методы DOM, которые нужны для разбора и обработки XML-файла. При создании объекта-парсера могут генерироваться исключения, которые необходимо перехватывать. Далее можно создавать объект типа Document, загружать данные из файла с именем, например, fileName и осуществлять его разборку:

try {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(fileName);
    . . .

После обхода и модификации дерева его можно сохранить в другом файле.

Использование DOM рассмотрим на примере предыдущего файла (Hello.xml). Следующая программа выводит на консоль текст атрибута, изменяет его и сохраняет в новом XML-документе:

package ua.inf.iwanoff.xml;

import java.io.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

public class HelloDOM {

    public static void main(String[] args) throws Exception {
        Document doc; // ссылка на объект "документ"
        // Создаем "построитель документов" с помощью "фабричного метода":
        DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        doc = db.parse(new File("Hello.xml"));
        // Находим корневой тег:
        Node rootNode = doc.getDocumentElement();
        // Просматриваем все "дочерние" теги:
        for (int i = 0; i < rootNode.getChildNodes().getLength(); i++) {
            Node currentNode = rootNode.getChildNodes().item(i);
            if (currentNode.getNodeName().equals("Hello")) {
                // Просматриваем все атрибуты:
                for (int j = 0; j < currentNode.getAttributes().getLength(); j++) {
                    if (currentNode.getAttributes().item(j).getNodeName().equals("Text")) {
                        // Нашли нужный атрибут. Выводим текст атрибута - приветствие:
                        System.out.println(currentNode.getAttributes().item(j).getNodeValue());
                        // Изменяем содержимое атрибута:
                        currentNode.getAttributes().item(j).setNodeValue("Привет, здесь был DOM!");
                        // Дальнейший поиск нецелесообразен:
                        break;
                    }
                }
                // Изменяем текст:
                System.out.println(currentNode.getTextContent());
                currentNode.setTextContent("\n    Привет, здесь тоже был DOM!\n");
                break;
            }     
        }
        // Создание объекта-преобразователя (в данном случае - для записи в файл).
        // Используем фабричный метод:
        Transformer transformer = TransformerFactory.newInstance().newTransformer();
        // Запись в файл:
        transformer.transform(new DOMSource(doc), 
            new StreamResult(new FileOutputStream(new File("HelloDOM.xml"))));
    }
  
}

После выполнения программы в файле проекта можно будет найти следующий файл (HelloDOM.xml):

<?xml version="1.0" encoding="UTF-8" standalone="no"?><Greetings>
    <Hello Text="Привет, здесь был DOM!">
        Привет, здесь тоже был DOM!
    </Hello>
</Greetings>

В приведенном примере для сохранения измененного документа в файле используется класс javax.xml.transform.Transformer. В общем случае этот класс используется при реализации так называемого XSLT-преобразования. XSLT (eXtensible Stylesheet Language Transformations) – язык преобразований XML-документов в другие XML-документы или другие объекты, такие как HTML, обычный текст и т. д. XSLT-процессор принимает один или несколько XML-документов источника, а также один или несколько модулей преобразования, и обрабатывает их для получения выходного документа. Преобразование содержит набор правил шаблона: инструкции и другие директивы, которыми руководствуется XSLT-процессор при генерации выходного документа.

1.1.4 Использование шаблона документа и схемы документа

Структурированные данные, которые могут быть представленными в форме XML-файла, требуют дополнительной информации. Наиболее распространены два основных формата представления такой информации – Определение шаблона документа (DTD) и Схема документа (XSD).

DTD (Document Template Definition) – набор правил, которые позволяют однозначно определить структуру определенного класса XML-документов. Директивы DTD могут быть присутствующими как в заголовке самого XML-документа (internal DTD), так и в другом файле (external DTD). Наличие DTD не является обязательным. Тем не менее, XML-документ, который не содержит DTD, или не указывает на него, не может считаться "правильным" (valid).

Использование шаблона документа можно продемонстрировать на следующем примере. Предположим, мы имеем такой XML-файл:

<?xml version="1.0" encoding="UTF-8"?>
<Pairs>
    <Pair>
        <x>1</x>
        <y>4</y>
    </Pair>
    <Pair>
        <x>2</x>
        <y>2</y>
    </Pair>
        . . .
</Pairs>

В нашем случае DTD-файл будет иметь такое содержимое:

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT Pair (x, y)>
<!ELEMENT x (#PCDATA)>
<!ELEMENT y (#PCDATA)>
<!ELEMENT Pairs (Pair+)>

Знак плюс в последней строке означает, что элементов Pair внутри тега Pairs может быть один или много. Это существенно с точки зрения следующей генерации классов. Кроме +, можно также использовать * (0 или много), вопросительный знак (0 или 1). Отсутствие знака означает, что элементов должно быть ровно один.

В приведенном примере вместо #PCDATA можно использовать NMTOKEN. Это означает, что в соответствующем месте документа может находиться одна лексема. Для описания атрибутов используется тег ATTLIST.

Для того, чтобы исходный документ считался правильным, к нему следует добавить ссылку на файл шаблона:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Pairs SYSTEM "YMax/Pairs.dtd">
. . .

XML Schema представляет собой альтернативный DTD способ задания структуры документа. Схема удобнее, чем DTD тем, что описание структуры документа выполнено на самом XML, в то время как DTD использует директивы, синтаксис которых не имеет ничего общего с синтаксисом XML. Кроме того, XML схема своими возможностями существенным образом превосходит DTD. Например, в схеме можно указывать типы тегов и атрибутов, определять ограничения и т.д.

XML-документ, который является правильно оформленным, ссылается на грамматические правила и полностью им соответствует, называется валидным (valid) документом.

Для того чтобы предотвратить конфликты имен тегов, в XML можно создавать так называемые пространства имен. Пространство имен определяет префикс, связанный с определенной схемой документа и прилагается к тегам. Собственное пространство имен определяется с помощью конструкции вроде следующей:

<root xmlns:pref="http://www.someaddress.org/">

В этом примере root – корневой тег XML-документа, pref – префикс, который определяет пространство имен, "http://www.someaddress.org/" – некоторый адрес, например, доменное имя автора схемы. Программы, которые обрабатывают XML-документы, никогда не проверяют этот адрес. Он необходим только для обеспечения уникальности пространства имен.

Непосредственно схема использует пространство имен xs.

Использование схемы документа можно продемонстрировать на следующем примере. Предположим, мы имеем такой XML-файл:

<?xml version="1.0" encoding="Windows-1251" ?>
<Student Name="Джон" Surname="Смит">
    <Marks>
        <Mark Subject="Математика" Value="4"/>
        <Mark Subject="Физика" Value="5"/>
        <Mark Subject="Программирование" Value="3"/>
    </Marks>
    <Comments>
        Не наш студент
    </Comments>
</Student>

Создание файла схемы следует начинать со стандартной конструкции:

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
   . . .
</xs:schema>

Между тегами <xs:schema> и </xs:schema> будет расположена информация о схеме документа. Для того, чтобы описать теги документа, внутри него можно добавлять стандартные теги. Для сложных тегов, в которые вкладываются другие, или которые имеют параметры:

<xs:element name="имя тега">
     <xs:complexType>
       . . .
     </xs:complexType>
</xs:element>

Внутри тега можно разместить список элементов:

<xs:sequence>
   . . .
</xs:sequence>

Можно разместить ссылки на другой тег:

<xs:element ref="имя другого тега"/>

Для элементов, которые непосредственно содержат данные, используют такой тег

<xs:element name="имя тега" type="имя типа"/>

Имена типов – это стандартные имена, некоторые из которых приведенные в следующей таблице:

Имя
Описание
xs:string Строка символов как последовательность 10646 символов Unicode или ISO/IEC, включая пробел, символ табуляции, возврат каретки и перевод строки
xs:integer Целое значение
xs:boolean Бинарные логические значения: true или false, 1 или 0.
xs:float 32-битное число с плавающей запятой
xs:double 64-битное число с плавающей запятой
xs:anyURI Универсальный идентификатор ресурса (Uniform Resource Identifier)

Тег

<xs:attribute name="имя атрибута" type="имя типа" />

позволяет описывать атрибуты.

Существует также большое количество дополнительных параметров тегов. Параметр maxOccurs задает максимальное количество вхождений элемента, minOccurs задает минимальное количество вхождений элемента, unbounded определяет неограниченное количество вхождений, required определяет обязательное вхождение, mixed задает элемент как имеющий смешанный тип и т.д.

Для нашего студента можно предложить такой файл схемы (Student.xsd):

<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="Student">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Comments" type="xs:string"/>
        <xs:element name="Marks">
          <xs:complexType>
            <xs:sequence>
              <xs:element ref="Mark" maxOccurs="unbounded"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
      <xs:attribute name="Name" type="xs:string" />
      <xs:attribute name="Surname" type="xs:string" />
    </xs:complexType>
  </xs:element>
  <xs:element name="Mark">
    <xs:complexType>
      <xs:attribute name="Subject" type="xs:string" />
      <xs:attribute name="Value" type="xs:string" />
    </xs:complexType>
  </xs:element>
</xs:schema>

Примечание: в IntelliJ IDEA имеется возможность генерации схемы по XML-документу с помощью соответствующей функции контекстного меню.

1.2 Технология связывания данных

Использование языка Java предусматривает удобный способ работы с XML-файлами – механизм связывания данных. Этот механизм предусматривает генерацию набора классов, которые описывают элементы файла и создания соответствующей структуры объектов в памяти.

Средство связывания данных XML содержит компилятор схемы, который транслирует схему в набор специфичных для схемы классов с соответствующими методами доступа и изменения (т.е. get и set). Он также содержит механизм маршализации (записи структурированных данных в XML-документ), поддерживает демаршализацию XML документов в соответствующую структуру взаимосвязанных экземпляров. Автоматически созданной структурой данных можно пользоваться без ручного размещения данных в списках или массивах.

Традиционно первой технологией связывания данных была технология Castor. Позже был стандартизирован API JAXB (Java Architecture for XML Binding). Версия 2 спецификации JAXB предполагает как генерацию классов по схеме, так и генерацию схемы по существующей структуре классов.

Для поддержки стандартных технологий API JAXB в среде IntelliJ IDEA Community Edition необходимо выполнить некоторые настройки. Один из путей реализации технологии JAXB - подключение утилиты xjc.exe, входящей в набор средств JDK. Эту утилиту можно запускать в командной строке, однако целесообразнее настроить контекстное меню. В окне Settings выбираем Tools | External Tools и нажимаем кнопку "+". В открывшемся диалоговом окне Edit Tool вводим имя (Name:) новой команды Generate JAXB Classes, путь к утилите xjc.exe (Program:), который на конкретном компьютере следует выбрать в диалоговом окне выбора файлов (кнопка "...") и параметры (Parameters:), которые в нашем случае будут такими:

-p $FileFQPackage$ -d "$SourcepathEntry$" "$FilePath$" 

Примечание. Для поддержки стандартых технологий API JAXB в среде Eclipse 2018-12 должны быть установлены средства Dali Java Persistence Tools. Если в Eclipse не установлены необходимые программные средства, их можно добавить с помощью главного меню Eclipse Help | Install New Software, далее в строке Work with: выбираем 2018-12 - http://download.eclipse.org/releases/2018-12. Далее находим в списке Web, XML, Java EE and OSGi Enterprise Development, в подсписке выбираем Dali Java Persistence Tools - JPA Diagram Editor и нажимаем Finish. Для работы генератора классов требуется JDK, а не JRE. В опциях Eclipse (Window | Preferences) выбираем Java | Installed JREs, далее Add..., Next, Directory... Необходимо выбрать установленную ранее JDK и нажать Finish. После этого в списке установленных JRE, которую нужно выбрать по умолчанию и нажать OK. Если проект был создан ранее, ему следует установить JDK в качестве JRE по умолчанию в опциях проекта (Project | Properties | Java Build Path далее Edit... и Alternate JRE, в соответствующей строке выбираем установленную ранее JDK).

Теперь можно создать новый проект, затем пакет, и поместить в него файл схемы со следующим содержанием:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="Greetings">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="Hello" >
                    <xs:complexType>
                        <xs:attribute name="Text" type="xs:string" use="required" />
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>
</xs:schema>

Oсуществляем генерацию классов с использованием контекстного меню (External Tools | Generate JAXB Classes).

Примечание. В среде Eclipse необходимо выбрать файл xsd в дереве Package Explorer. В контекстном меню выбираем Generate | JAXB Classes. Далее в мастере генерации классов указываем проект, пакет и другие дополнительные сведения, если необходимо. В случае успешного завершения генерации в указанном пакете появятся сгенерированные классы.

Сгенерированы такие классы. Класс ObjectFactory:

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2015.11.28 at 01:26:26 PM EET 
//


package ua.inf.iwanoff.xml;

import javax.xml.bind.annotation.XmlRegistry;


/**
 * This object contains factory methods for each 
 * Java content interface and Java element interface 
 * generated in the ua.inf.iwanoff.xml package. 
 * <p>An ObjectFactory allows you to programatically 
 * construct new instances of the Java representation 
 * for XML content. The Java representation of XML 
 * content can consist of schema derived interfaces 
 * and classes representing the binding of schema 
 * type definitions, element declarations and model 
 * groups.  Factory methods for each of these are 
 * provided in this class.
 * 
 */
@XmlRegistry
public class ObjectFactory {


    /**
     * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: ua.inf.iwanoff.xml
     * 
     */
    public ObjectFactory() {
    }

    /**
     * Create an instance of {@link Greetings }
     * 
     */
    public Greetings createGreetings() {
        return new Greetings();
    }

    /**
     * Create an instance of {@link Greetings.Hello }
     * 
     */
    public Greetings.Hello createGreetingsHello() {
        return new Greetings.Hello();
    }

}

Класс Greetings:

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.8-b130911.1802 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2015.11.28 at 01:26:26 PM EET 
//


package ua.inf.iwanoff.xml;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for anonymous complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * &lt;complexType>
 *   &lt;complexContent>
 *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       &lt;sequence>
 *         &lt;element name="Hello">
 *           &lt;complexType>
 *             &lt;complexContent>
 *               &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *                 &lt;attribute name="Text" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
 *               &lt;/restriction>
 *             &lt;/complexContent>
 *           &lt;/complexType>
 *         &lt;/element>
 *       &lt;/sequence>
 *     &lt;/restriction>
 *   &lt;/complexContent>
 * &lt;/complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "", propOrder = {
    "hello"
})
@XmlRootElement(name = "Greetings")
public class Greetings {

    @XmlElement(name = "Hello", required = true)
    protected Greetings.Hello hello;

    /**
     * Gets the value of the hello property.
     * 
     * @return
     *     possible object is
     *     {@link Greetings.Hello }
     *     
     */
    public Greetings.Hello getHello() {
        return hello;
    }

    /**
     * Sets the value of the hello property.
     * 
     * @param value
     *     allowed object is
     *     {@link Greetings.Hello }
     *     
     */
    public void setHello(Greetings.Hello value) {
        this.hello = value;
    }


    /**
     * <p>Java class for anonymous complex type.
     * 
     * <p>The following schema fragment specifies the expected content contained within this class.
     * 
     * <pre>
     * &lt;complexType>
     *   &lt;complexContent>
     *     &lt;restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
     *       &lt;attribute name="Text" use="required" type="{http://www.w3.org/2001/XMLSchema}string" />
     *     &lt;/restriction>
     *   &lt;/complexContent>
     * &lt;/complexType>
     * </pre>
     * 
     * 
     */
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "")
    public static class Hello {

        @XmlAttribute(name = "Text", required = true)
        protected String text;

        /**
         * Gets the value of the text property.
         * 
         * @return
         *     possible object is
         *     {@link String }
         *     
         */
        public String getText() {
            return text;
        }

        /**
         * Sets the value of the text property.
         * 
         * @param value
         *     allowed object is
         *     {@link String }
         *     
         */
        public void setText(String value) {
            this.text = value;
        }

    }

}

В приведенном коде аннотации управляют представлением данных в XML-документе.

В корне проекта размещаем файл Hello2.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<Greetings>
    <Hello Text="Привет, XML!" />
</Greetings>    

Теперь можно создать класс HelloJAXB и в его функции main() осуществить действия по загрузке документа, чтению и изменению значения атрибута и записи в новый файл:

package ua.inf.iwanoff.xml;

import java.io.*;
import javax.xml.bind.*;
public class HelloJAXB {

    public static void main(String[] args) {
        try {
            // Через объект класса JAXBContext обеспечивается доступ к средствам JAXB API
            // Указываем пакет с необходимыми классами:
            JAXBContext jaxbContext = JAXBContext.newInstance("ua.inf.iwanoff.xml");
            // Читаем данные из файла и загружаем в объект сгенерированного класса:
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            Greetings greetings = (Greetings)unmarshaller.unmarshal(new FileInputStream("Hello2.xml"));
            // Выводим старое значение атрибута:
            System.out.println(greetings.getHello().getText());
            // Меняем значение атрибута:
            greetings.getHello().setText("Привет, JAXB!");
            // Создаем объект-Marshaller для вывода в файл:
            Marshaller marshaller = jaxbContext.createMarshaller();
            // "Включаем" форматирование:
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            // Сохраняем объект в новом файле:
            marshaller.marshal(greetings, new FileWriter("HelloJAXB.xml"));
        }
        catch (JAXBException | IOException e) {
            e.printStackTrace();
        }
    }

}

Новый файл HelloJAXB.xml будет иметь вид:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Greetings>
    <Hello Text="Привет, JAXB!"/>
</Greetings>

Как видно из примера, технология связывания данных обеспечивает более качественное форматирование XML-документа.

1.3 Сериализация в XML-файлы

Главным недостатком описанной ранее стандартной сериализации (классы ObjectOutputStream и ObjectInputStream) является необходимость работы с двоичными (нетекстовыми) файлами. Обычно такие файлы используют не для долгосрочного хранения данных, а для единовременного хранения и восстановления объектов. Безусловно, более удобной и управляемой является сериализация в текстовый файл, в частности, в XML-документ. Существует несколько подходов к сериализации и десериализации, построенной на XML. Наиболее простым является подход, основанный на использовании классов java.beans.XMLEncoder и java.beans.XMLDecoder. Наиболее естественное применение этих классов – хранение и воспроизведение элементов графического интерфейса. Но можно также хранить объекты других классов, удовлетворяющих спецификации Java Beans.

Java Bean - это класс, удовлетворяющий следующим требованиям:

  • класс открытый (public)
  • отсутствуют открытые данные (открытыми могут быть только методы)
  • класс должен реализовывать интерфейс java.io.Serializable
  • пара методов с именами setNnn() и getNnn() образуют свойство с именем nnn и соответствующим типом. Для свойств типа boolean наиболее часто используют "is" вместо "get".

Ранее были реализованы классы Line и Point. XML-сериализация не требует реализации интерфейса Serializable. Однако классы должны быть открытыми, иметь открытые функции доступа (геттеры и сеттеры) к закрытым полям. Класс Point:

package ua.inf.iwanoff.xml;

public class Point implements java.io.Serializable {
    private static final long serialVersionUID = -3566722853756147165L;
    private double x, y;

    public void setX(double x) {
        this.x = x;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

}

Класс Line:

package ua.inf.iwanoff.xml;

public class Line implements java.io.Serializable {
    private static final long serialVersionUID = 5364062177715773963L;
    private Point first = new Point(), second = new Point();

    public void setFirst(Point first) {
        this.first = first;
    }

    public Point getFirst() {
        return first;
    }

    public Point getSecond() {
        return second;
    }

    public void setSecond(Point second) {
        this.second = second;
    }

}

Можно предложить такой код, который обеспечивает XML-сериализацию:

package ua.inf.iwanoff.xml;

import java.beans.XMLEncoder;
import java.io.*;

public class XMLSerialization {

    public static void main(String[] args) {
        Line line = new Line();
        line.getFirst().setX(1);
        line.getFirst().setY(2);
        line.getSecond().setX(3);
        line.getSecond().setY(4);
        try (XMLEncoder xmlEncoder = new XMLEncoder(new FileOutputStream("Line.xml"))) {
            xmlEncoder.writeObject(line);
            xmlEncoder.flush();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

После выполнения программы мы получим такой XML-файл:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.7.0_51" class="java.beans.XMLDecoder">
 <object class="ua.inf.iwanoff.xml.Line">
  <void property="first">
   <void property="x">
    <double>1.0</double>
   </void>
   <void property="y">
    <double>2.0</double>
   </void>
  </void>
  <void property="second">
   <void property="x">
    <double>3.0</double>
   </void>
   <void property="y">
    <double>4.0</double>
   </void>
  </void>
 </object>
</java>    

Теперь можно осуществить десериализацию с помощью такого кода:

package ua.inf.iwanoff.xml;

import java.beans.XMLDecoder;
import java.io.*;

public class XMLDeserialization {

    public static void main(String[] args) {
        try (XMLDecoder xmlDecoder = new XMLDecoder(new FileInputStream("Line.xml"))) {
            Line line = (Line)xmlDecoder.readObject();
            System.out.println(line.getFirst().getX() + " " + line.getFirst().getY() + " " +
                               line.getSecond().getX() + " " + line.getSecond().getY());
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Существуют также другие (нестандартные) реализации XML-сериализации. Одно из наиболее популярных решений - использование библиотеки XStream.

 

 

2 Примеры программ

2.1 Использование технологии DOM

Предположим, подготовлен XML-документ с данными о континенте (Continent.xml):

<?xml version="1.0" encoding="UTF-8"?>
<ContinentData Name="Европа">
    <CountriesData>
        <CountryData Name="Украина" Area="603700" Population="46314736" >
            <CapitalData Name="Киев" />
        </CountryData>
        <CountryData Name="Франция" Area="547030" Population="61875822" >
            <CapitalData Name="Москва" />
        </CountryData>
        <CountryData Name="Германия" Area="357022" Population="82310000" >
            <CapitalData Name="Берлин" />
        </CountryData>
    </CountriesData>
</ContinentData>    

Примечание: ошибка со столицей Франции сделана умышленно.

Необходимо средствами DOM прочитать данные, исправить ошибку и сохранить в новом файле. Программа будет иметь следующий вид:

package ua.inf.iwanoff.xml;

import java.io.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

public class ContinentWithDOM {

    public static void main(String[] args) {
        try {
            Document doc;
            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            doc = db.parse(new File("Continent.xml"));
            Node rootNode = doc.getDocumentElement();
          mainLoop: 
            for (int i = 0; i < rootNode.getChildNodes().getLength(); i++) {
                Node countriesNode = rootNode.getChildNodes().item(i);
                if (countriesNode.getNodeName().equals("CountriesData")) {
                    for (int j = 0; j < countriesNode.getChildNodes().getLength(); j++) {
                        Node countryNode = countriesNode.getChildNodes().item(j);
                        if (countryNode.getNodeName().equals("CountryData")) {
                            // Находим атрибут по имени:
                            if (countryNode.getAttributes().getNamedItem("Name").getNodeValue().equals("Франция")) {
                                for (int k = 0; k < countryNode.getChildNodes().getLength(); k++) {
                                    Node capitalNode = countryNode.getChildNodes().item(k);
                                    if (capitalNode.getNodeName().equals("CapitalData")) {
                                        capitalNode.getAttributes().getNamedItem("Name").setNodeValue("Париж");
                                        break mainLoop;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.transform(new DOMSource(doc), 
                new StreamResult(new FileOutputStream(new File("CorrectedConinent.xml"))));
        }
        catch (Exception  e) {
            e.printStackTrace();
        }
    }

}

2.2 Использование JAXB

Предыдущую задачу можно решить с помощью технологии JAXB. Для JAXB-классов создаем отдельный вложенный пакет continent внутри текущего пакета. В него помещаем схему документа (Continent.xsd):

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:element name="ContinentData">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="CountriesData">
                    <xs:complexType>
                        <xs:sequence>
                            <xs:element maxOccurs="unbounded" name="CountryData">
                                <xs:complexType>
                                    <xs:sequence>
                                        <xs:element name="CapitalData">
                                            <xs:complexType>
                                                <xs:attribute name="Name" type="xs:string" use="required" />
                                            </xs:complexType>
                                        </xs:element>
                                    </xs:sequence>
                                    <xs:attribute name="Name" type="xs:string" use="required" />
                                    <xs:attribute name="Area" type="xs:unsignedInt" use="required" />
                                    <xs:attribute name="Population" type="xs:unsignedInt" use="required" />
                                </xs:complexType>
                            </xs:element>
                        </xs:sequence>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
            <xs:attribute name="Name" type="xs:string" use="required" />
        </xs:complexType>
    </xs:element>
</xs:schema>    

Теперь можно сгенерировать классы. Для них создаем новый пакет continent внутри текущего пакета. Программа, использующая эти классы, будет иметь следующий вид:

package ua.inf.iwanoff.xml;

import java.io.*;
import javax.xml.bind.*;
import ua.inf.iwanoff.xml.continent.*;

public class ContinentWithJAXB {

    public static void main(String[] args) {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance("ua.inf.iwanoff.xml.continent");
            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            ContinentData data = (ContinentData)unmarshaller.unmarshal(new FileInputStream("Continent.xml"));
            // Работаем со списком элементов CountryData:
            for (ContinentData.CountriesData.CountryData c : data.getCountriesData().getCountryData()) {
                if (c.getName().equals("Франция")) {
                    c.getCapitalData().setName("Париж");
                    break;
                }
            }
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            marshaller.marshal(data, new FileWriter("CorrectedContinent.xml"));
        }
        catch (JAXBException | IOException e) {
            e.printStackTrace();
        }
    }

}

3 Задания на самостоятельную работу

3.1 Использование технологии SAX*

Подготовить XML-документ с данными о студентах академической группы. С помощью технологии SAX осуществить чтение данных из XML-документа и вывод данных на консоль

3.2 Использование технологии DOM*

Подготовить XML-документ с данными о студентах академической группы. С помощью технологии DOM осуществить чтение данных из XML-документа, модификацию данных и запись их в новый документ.

3.3 Данные о городе*

Подготовить XML-документ, который описывает данные о городе. Сгенерировать классы с помощью технологии связывания данных (JAXB). Осуществить чтение данных из XML-документа, модификацию данных и запись их в новый документ.

3.4 Реализация сериализации и десериализации с использованием XML*

Разработать классы Студент и Академическая группа создать объекты, осуществить их XML-сериализацию и десериализацию.

4 Контрольные вопросы

  1. Для каких целей используются XML-документы?
  2. Какие ограничения накладываются на структуру XML-документа, синтаксис и расположение тегов?
  3. Чем отличаются технологии SAX и DOM?
  4. Каким образом осуществляется чтение и запись XML-документов?
  5. Что такое XSLT?
  6. Чем отличается валидный (valid) и правильно оформленный (well-formed) XML-документ?
  7. Чем отличаются шаблоны документа и схемы документа?
  8. Является ли шаблон документа XML-документом?
  9. Является ли схема документа XML-документом?
  10. Для чего в XML-документах необходимые пространства имен?
  11. В чем преимущества технологии связывания данных?
  12. Какие существуют реализации технологии связывания данных?
  13. Что такое маршализация и демаршализация?
  14. Какие классы соответствуют спецификации Java Beans?
  15. Какие преимущества и недостатки сериализации в XML?

 

up