04.11.2012

SAX и DOM парсинг в Java - примеры и различия подходов.

Если вам нужно загрузить данные из XML документа в java объект - можно использовать разные способы связывания данных, один из них - JAXB, я уже неоднократно упоминал. Подобные варианты подходят, если заранее известен формат исходного XML документа. А если вам необходим парсинг XML произвольного формата, то здесь можно использовать SAX или DOM парсеры. Эти парсеры аналогичны по функциональности, различаются подходы -  DOM парсер сначала загружает весь исходный документ в java объект, с которым затем можно работать; SAX парсер использует событийную модель разбора документа, парсинг происходит "на лету" и загружать в память сразу весь XML нет необходимости. Давайте посмотрим на конкретные примеры использования таких парсеров. Итак, вот наш документ для разбора:
<test-object>
<items type="available browsers">
<item>
Firefox
</item>
<item>
Opera
</item>
<item>
IE
</item>
<item>
Chrome
</item>
</items>
<use-browser version="12">
Opera
</use-browser>
</test-object>

Для того, чтобы использовать SAX парсер, нужно определить класс, расширяющий DefaultHandler и переопределить некоторые из его методов. Именно этот класс и определяет формат выходных данных:

package test;

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

public class TestSAXHandler extends DefaultHandler {

private StringBuffer result;

public TestSAXHandler(){
result = new StringBuffer();
}

@Override
public void startElement(String namespaceURI, String localName,
String qName, Attributes atts) throws SAXException {

//имя тега
result.append("Element name = '"+ qName+"'\n");

//атрибуты тега
for (int i = 0; i < atts.getLength(); i++){
result.append("Attribute name = '" +
atts.getQName(i) + "'; Attribute value = '" + atts.getValue(i)+"'\n");
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
String value = "";

//содержимое тега
for (int i = start; i < length; i++){
value += ch[i];
}

if (value.length() != 0)
result.append("Element content = '" + value.trim() + "'\n");
}

@Override
public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {

//закрытие тега
result.append("Element closed, name = '" + qName + "'\n");
}

public String getResult(){
return result.toString();
}
}

SAX парсер работает в данном случае так - когда в исходном документе встречается открывающий тег, вызывается метод startElement с соответствующими параметрами. При чтении содержимого тега вызывается characters. При закрытии тега - endElement. Таким образом документ и разбирается без предварительной его загрузки в память. Для использования DOM парсера просто создадим класс, работающий с объектом типа Document и определяющим формат выходных данных. Здесь никаких событий нет, есть уже загруженный объект типа Document, который является представлением исходных данных.

package test;

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

public class DomParser {

public String parse(Document input) {
//передаем корневой элемент XML документа
return parseNode(input.getDocumentElement());
}

private String parseNode(Node node) {

//если текущий элемент - текстовое содержимое тега
if (node.getNodeName().equals("#text"))
return "";

StringBuffer result = new StringBuffer();

//имя тега
result.append("Element name = '" + node.getNodeName() + "'\n");

//атрибуты тега
NamedNodeMap nodeMap = node.getAttributes();

if (nodeMap != null) {
for (int i = 0; i < nodeMap.getLength(); i ++){
result.append("Attribute name = '" + nodeMap.item(i).getNodeName() 
+ "'; Attribute value = '" + nodeMap.item(i).getNodeValue() + "'\n");
}
}

//содержимое элемента
if (getElementContent(node) != null && !(getElementContent(node).equals("")))
result.append("Element content = '" + getElementContent(node)+"'\n");

NodeList nodeList = node.getChildNodes();

//рекурсивно вызываем метод для каждого из подэлементов в 
//переданном элементе
for (int i = 0; i < nodeList.getLength(); i++){
result.append(parseNode(nodeList.item(i)));
}

//закрытие тега
result.append("Element closed, name = '" + node.getNodeName() + "'\n");

return result.toString();
}

private String getElementContent(Node node) {

Node contentNode = node.getFirstChild();
if (contentNode != null)

if (contentNode.getNodeName().equals("#text")) {
String value = contentNode.getNodeValue();
if (value != null)
return value.trim();
}
return null;
}
}

Теперь осталось только загрузить исходные данные и использовать реализованные парсеры:

package test;

import java.io.File;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.w3c.dom.Document;

public class Main {

private final static String PATH = "C:\\Users\\alex\\test.xml";

public static void main(String args[]){

//Входной файл, содержащий XML документ
File input = new File(PATH);

//SAX парсер
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
TestSAXHandler handler = new TestSAXHandler();
parser.parse(input, handler);
System.out.println("SAX parser result:\n" + handler.getResult());
} catch (Exception e) {
e.printStackTrace();
}

//DOM парсер
try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory
.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();   
Document doc = dBuilder.parse(input);
doc.getDocumentElement().normalize();
DomParser domParser = new DomParser();
System.out.println("DOM parser result:\n" + domParser.parse(doc));

} catch (Exception e) {
e.printStackTrace();
}
}
}

Результат парсинга для первого и второго вариантов выглядит аналогично:

Element name = 'test-object'
Element name = 'items'
Attribute name = 'type'; Attribute value = 'available browsers'
Element name = 'item'
Element content = 'Firefox'
Element closed, name = 'item'
Element name = 'item'
Element content = 'Opera'
Element closed, name = 'item'
Element name = 'item'
Element content = 'IE'
Element closed, name = 'item'
Element name = 'item'
Element content = 'Chrome'
Element closed, name = 'item'
Element closed, name = 'items'
Element name = 'use-browser'
Attribute name = 'version'; Attribute value = '12'
Element content = 'Opera'
Element closed, name = 'use-browser'
Element closed, name = 'test-object'

Какой парсер лучше использовать в вашем случае - SAX или DOM, можно решить по типу исходных данных. Если исходный документ большой по объему и вложенность элементов невысока - скорее всего SAX, при этом подходе разбор исходных данных происходит несколько быстрее и используется меньше памяти. Если нужно преобразовать сложную структуру данных - наверное удобнее и проще будет работать с DOM.

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



Теги: programming java

comments powered by Disqus