XML документ - DOM, SAXParser

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

Для работы с XML-файлами Java располагает достаточно большим набором инструментов, начиная от встроенных возможностей, которые предоставляет Core Java, и заканчивая большим набором разнообразного стороннего кода, оформленного в виде библиотек. Сначала рассмотрим использование DOM для чтения XML-файла и создания нового файла/документа. А в заключение будет приведено описание и применение SAX-парсера SAXParser.

XML документ представляет собой набор узлов (тегов). Каждый узел может иметь неограниченное количество дочерних узлов, которые, в свою очередь, также могут содержать потомков или не содержать их совсем. Таким образом строится дерево объектов. DOM - это объектная модель документа, которая представляет собой это дерево в виде специальных объектов/узлов org.w3c.dom.Node. Каждый узел Node соответствует своему XML-тегу и содержит полную информацию о том, что это за тег, какие он имеет атрибуты, какие дочерние узлы содержит внутри себя и т.д. На самой вершине этой иерархии находится Document, который является корневым элементов дерева.

Чтение XML-файла

Получить объект Document XML-файла можно следующим образом :

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder        db  = dbf.newDocumentBuilder();
Document               doc = db.parse(new File("data.xml"));

Чтобы найти какой-либо узел в дереве можно использовать метод getElementsByTagName, который возвращает список всех элементов :

NodeList org.w3c.dom.Document.getElementsByTagName(String tagname)
. . .
// Пример использования метода getElementsByTagName
NodeList nodeList = doc.getElementsByTagName("tagname");

Метод getElementsByTagName является case-sensitive, т.е. различает прописные и строчные символы.

В цикле можно просмотреть все дочерние узлы. C помощью метода getAttributes можно узнать атрибуты узла. Метод getNodeType позволяет проверить тип узла :

NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
    // дочерний узел
    Node node = children.item(i);
    if (node.getNodeType() == Node.ELEMENT_NODE) {
        // атрибуты узла
        NamedNodeMap attributes = node.getAttributes();
        Node nameAttrib = attributes.getNamedItem("attribute_name");
        System.out.println ("" + i + ". " + nameAttrib.getNodeValue());
    }
}

Создание XML-файла

Для создания нового объекта Document используйте следующий код :

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder        db  = dbf.newDocumentBuilder();
Document               doc = db.newDocument();

Элемент Element объекта Document создается с использованием метода createElement. Для определения значения элемента следует использовать метод setTextContent. Для добавления элемента в узловую запись используйте метод appendChild (Node). Элемент может содержать атрибуты. Чтобы добавить к элементу атрибут следует использовать метод setAttribute. Если элемент уже содержит атрибут, то его значение изменится.

Element org.w3c.dom.Document.createElement(String s) throws DOMException;

void org.w3c.dom.Node.setTextContent(String text) throws DOMException;

Node org.w3c.dom.Node.appendChild(Node newChild) throws DOMException;

void org.w3c.dom.Element.setAttribute(String name, 
                                      String value) throws DOMException;

// Пример
Element root = doc.createElement("Users");

Element user = doc.createElement("user");
user.setTextContent("Остап Бендер");
user.setAttribute ("book", "12 стульев");
root.appendChild(user);
doc.appendChild(root);

В результате работы примера будет создан Document следующей структуры :

<?xml version="1.0" encoding="UTF-8"?>
<Users>
    <user book="12 стульев">Остап Бендер</user>
</Users>

Пример чтения и создания XML-файла

Для чтения готового XML-файла и формирования нового файла создадим в IDE Eclipse простой проект XMLSample, структура которого представлена на следующем скриншоте.

Проект включает XML-файл "posts.xml" с исходными данными, создаваемый XML-файл данных "data.xml", класс Post.java, в который будут упаковываться отдельные записи массива данных и основной класс проекта XMLSample, который будет производить все необходимые действия.

Структура XML-файла

<?xml version="1.0" encoding="UTF-8"?>
<ROOT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <row>
        <field name="forum_name">...</field>
        <field name="year_post">...</field>
        <field name="post_subject">...</field>
        <field name="MID(post_text, 1, 80)">...</field>
        <field name="username">...</field>
        <field name="post_time">...</field>
        <field name="post_time">...</field>
        <field name="post_subject_source">...</field>
    </row>
</ROOT>

В качестве исходных данных используется XML-файл "posts.xml" из примеров разработчиков Sencha GXT 3.1.1. Структура XML-данных содержит корневой элемент <ROOT> и набор объектов/сущностей, представленных тегами <row />.

Листинг класса Person

import java.util.Date;

public class Post
{
    private static int ID = 0;
  
    private int    id;
    private String username;
    private String subject;
    private String forum;
    private Date   date;

    public Post() {
        setId(ID++);
    }
}

Класс Person имеет несколько полей. Идентификатор записи id определяется при создании объекта в конструкторе. Методы set/get не представлены в листинге.

Чтение XML-файла

Для чтения XML-файла в проекте используется метод readDataXML(), который создает список persons типа List<Person>, читает XML-файл данных и формирует объект doc типа Document. После этого в цикле создается массив данных. Вспомогательная функция getValue извлекает текст атрибута записи.

import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

. . .
public class XMLSample
{
    private  final  String      FILE_post = "posts.xml";
    private         List<Post>  posts;

    private String getValue(NodeList fields, int index)
    {
        NodeList list = fields.item(index).getChildNodes();
        if (list.getLength() > 0) {
            return list.item(0).getNodeValue();
        } else {
            return "";
        }
    }
    private void readDataXML()
    {
        posts = new ArrayList<Post>();

        SimpleDateFormat       sdf = null;
        DocumentBuilderFactory dbf = null;
        DocumentBuilder        db  = null;
        Document               doc = null;
        try {
            sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            dbf = DocumentBuilderFactory.newInstance();
            db  = dbf.newDocumentBuilder();
            doc = null;

            FileInputStream fis = null; 
            if (fileExists(FILE_post)) {
                try {
                    fis = new FileInputStream(FILE_post);
                    doc = db.parse(fis);
                } catch (FileNotFoundException e){
                    e.printStackTrace();
                }
            }
            doc.getDocumentElement().normalize();

            NodeList nodeList = doc.getElementsByTagName("row");
            NodeList fields   = null;
            for (int s = 0; s < nodeList.getLength(); s++) {
                Node node = nodeList.item(s);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    Element fstElmnt = (Element) node;
                    fields = fstElmnt.getElementsByTagName("field");
                    Post p = new Post();
                    p.setForum   (getValue(fields, 0));
                    p.setDate    (sdf.parse(getValue(fields, 1)));
                    p.setSubject (getValue(fields, 2));
                    p.setUsername(getValue(fields, 4));
                    posts.add(p);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Следует обратить внимание, что для чтения значения атрибута записи (объекта Person) сначала получаем ссылку на массив тегов <field>, и после этого по индексу в функции getValue извлекаем значение.

Создание XML-файла

Для создания нового XML-файла на основе массива posts подготовим два списка данных типа List : пользователей users и форумов forums. Эти два массива запишем в XML-файл.

Создание нового объекта Document и сохранение его в XML-файл в проекте выполняет метод writeDataXML :

Листинг метода создания XML-файла

private  final  String  FILE_data = "data.xml";

private void writeDataXML()
{
    DocumentBuilderFactory dbf = null;
    DocumentBuilder        db  = null;
    Document               doc = null;
    try {
        dbf = DocumentBuilderFactory.newInstance();
        db  = dbf.newDocumentBuilder();
        doc = db.newDocument();

        Element e_root   = doc.createElement("Posts");
        e_root.setAttribute("lang", "en");
        Element e_users  = doc.createElement("Users");
        Element e_forums = doc.createElement("Forums");
        e_root.appendChild(e_users);
        e_root.appendChild(e_forums);
        doc.appendChild(e_root);
        if (posts.size() == 0)
            return;

        List<String> users  = new ArrayList<String>();
        List<String> forums = new ArrayList<String>();
        for (int i = 0; i < posts.size(); i++){
            if (!users.contains(posts.get(i).getUsername()))
                users.add(posts.get(i).getUsername());
            if (!forums.contains(posts.get(i).getForum()))
                forums.add(posts.get(i).getForum());
            }        System.out.println("    пользователей : " + users.size());
        for (String user : users) {
            Element e = doc.createElement("user");
            e.setTextContent(user);
            e_users.appendChild (e);
        }
        System.out.println("    форумов : " + forums.size());
        for (String forum : forums) {
            Element e = doc.createElement("forum");
            e.setTextContent(forum);
            e_forums.appendChild (e);
        }
    } catch (ParserConfigurationException e) {
        e.printStackTrace();
    } finally {
        // Сохраняем Document в XML-файл
        if (doc != null)
            writeDocument(doc, FILE_data);
    }
}

Процедура сохранения объекта Document в XML-файл представлена отдельным методом writeDocument :

Листинг процедуры сохранения XML-файла

/**
 * Процедура сохранения DOM в файл
 */ 
private void writeDocument(Document document, final String path) 
                            throws TransformerFactoryConfigurationError
{
    Transformer      trf = null;
    DOMSource        src = null;
    FileOutputStream fos = null;
    try {
        trf = TransformerFactory.newInstance().newTransformer();
        src = new DOMSource(document);
        fos = new FileOutputStream(path);
        
		StreamResult result = new StreamResult(fos);
        trf.transform(src, result);
    } catch (TransformerException e) {
        e.printStackTrace(System.out);
    } catch (IOException e) {
        e.printStackTrace(System.out);
    }
}

Если массив posts окажется пустым, то новый XML-файл должен будет иметь следующий вид :

<?xml version="1.0" encoding="UTF-8"?>
<Posts lang="en">
    <Users></Users>
    <Forums></Forums>
</Posts>

SAX-парсер, SAXParser

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

Рассмотрим пример SAXExample.java с использованием класса SAXParser для анализа XML-текста, представленного файлом phonebook.xml, содержащего 3 записи и имеющего следующий вид :

Файл phonebook.xml

<?xml version="1.0" encoding="UTF-8"?>
<phonebook>
    <person>
        <name>Остап Бендер</name>
        <email>ostap@12.com</email>
        <phone>999-987-6543</phone>
    </person>
    <person>
        <name>Киса Воробьянинов</name>
        <email>kisa@12.com</email>
        <phone>999-986-5432</phone>
    </person>
    <person>
        <name>Мадам Грицацуева</name>
        <email>madam@12.com</email>
        <phone>999-985-4321</phone>
    </person>
</phonebook>

Пакеты «javax.xml.parsers» и «org.xml.sax» включают набор классов для «разбора» XML в строковом представлении. К основным классам этих пакетов, с точки зрения разложения XML объекта на составляющие, относятся SAXParser и DefaultHandler.

В примере SAXExample.java создается класс handler типа DefaultHandler, в котором методы анализа XML-строки переопределяются. Все прозрачно.

Листинг SAXExample.java

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
 
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXExample
{
    final  String  fileName = "phonebook.xml";
    final  String  TAG_NAME = "name";

    DefaultHandler handler = new DefaultHandler() {
        boolean tagOn = false; // флаг начала разбора тега

        /** 
         * Метод вызывается, когда SAXParser начинает обработку тэга
         */
        @Override
        public void startElement(String uri, String localName, 
                                 String qName, Attributes attributes) 
                                                    throws SAXException {
            // Устанавливаем флаг, если тег имеет имя TAG_NAME
            tagOn = (qName.equalsIgnoreCase(TAG_NAME));
            System.out.println("\t<" + qName + ">");
        }

        /** 
         * Метод вызывается, когда SAXParser считывает текст между тэгами
         */
        @Override
        public void characters(char ch[], int start, int length)
                                                    throws SAXException {
            // Проверка флага
            if (tagOn) {
                // Флаг установлен
                System.out.println("\t\t" + new String(ch,start,length));
                tagOn = false;
            }
        }
        @Override
        public void endElement(String uri,String localName,String qName)
                                                      throws SAXException
        {
            super.endElement(uri, localName, qName);
        }

        @Override
        public void startDocument() throws SAXException
        {
            System.out.println("Начало разбора документа!");
        }

        @Override
        public void endDocument() throws SAXException
        {
            System.out.println("Разбор документа завершен!");
        }        
    };

    public SAXExample()
    {
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            SAXParser saxParser = factory.newSAXParser();
 
            // Стартуем разбор XML-документа
            saxParser.parse(fileName, handler);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String args[]) 
    {
        new SAXExample();
        System.exit(0);
    }
}

В результате выполнения примера использования SAXParser в консоль будут выведены следующие сообщения :


Начало разбора документа!
   <phonebook>
      <person>
      <name>
           Остап Бендер
      <email>
      <phone>
      <person>
      <name>
           Киса Воробьянинов
      <email>
      <phone>
      <person>
      <name>
           Мадам Грицацуева
      <email>
      <phone>
Разбор документа завершен!
 

Скачать примеры

Рассмотренные на странице примеры использования DOM для чтения и создания XML-документа, применения SAXParser'а для анализа XML-текста в виде проекта Eclipse можно скачать здесь (133 Кб).

  Рейтинг@Mail.ru