Статьи

Java и XML — часть 2 (JDOM2)

В моей первой статье я упомянул некоторые термины и опубликовал ссылки на Java и XML в целом. В этой статье я хочу написать кое-что о методе парсинга DOM и отличиях JDOM2 и парсера DOM от Java Standard Edition.

Метод синтаксического анализа DOM, также известный как DOM, является эталонной реализацией синтаксического анализатора W3C и первой реализацией такого рода обработки для Java, но он не единственный и не самый лучший. По этой причине другие парсеры были реализованы с использованием этого метода синтаксического анализа. 

Наиболее популярные и широко используемые синтаксические анализаторы, которые используют DOM в качестве объектов и также упоминаются на сайте Oracle Tutorial, но не являются частью Java SE, включают в себя:

Почему (JDOM) JDOM2 над DOM4J

Когда я искал другие парсеры, я нашел информацию о них на разных форумах. Это было несколько лет назад, и я не могу вспомнить, на каких форумах они были, иначе я бы разместил здесь ссылки. Лучшая информация того времени говорила мне, что DOM4J обладает большей функциональностью, чем JDOM, что не всегда необходимо.

В то время я также сотрудничал с другим магазином разработки, который также использовал парсер JDOM, так что, возможно, это повлияло и на мое решение. Окончательное решение о критериях заключалось в том, что JDOM был принят процессом сообщества Java (JCP) как запрос спецификации Java (JSR-102). Это были мои причины для выбора JDOM вместо DOM4J. Это не значит, что DOM4J — худший парсер, но JDOM был моим выбором.

JDOM2 против DOM

В начале одного из моих проектов я использовал DOM-парсер основного API Java. После того, как я получил больше информации о различных видах синтаксических анализаторов, я решил работать с JDOM, и весь старый код, в котором использовался DOM, нуждался в рефакторинге. Делая это, я заметил различия в синтаксисе синтаксических анализаторов, что было одной из причин, по которой я переписал код. Различия между парсерами описаны ниже.

Примечание . Одно из различий между JDOM и JDOM2 заключается в том, что JDOM2 использует дженерики, а JDOM — нет.

Примечание : один термин, который я хочу упомянуть здесь, который можно найти во многих руководствах, — это класс клиента . Java-класс, в котором используются парсеры XML, называется клиентским классом . Этот термин также используется при использовании других видов библиотек обработки, таких как анализаторы JSON или другой шаблон фабрики, такой как компоновщик.

Для создания исходного кода я использовал Eclipse 4.3 Kepler / Luna в качестве IDE, JDK 1.7u40 и JDOM2. Также был задействован Junit 4.8 для создания тестовых классов.

Пример XML

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

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:my="http://www.stojanok.name/2013/javaxml">
	<tag>first</tag>
	<tag type="special">second</tag>
	<other>third</other>
	<other type="">fourth</other>
	<!-- XPath -->
	<deep>
		<tag>
			<other>fifth</other>
		</tag>
	</deep>
	<!-- namespace -->
	<my:other>sixth</my:other>
	<my:other my:type="different">seventh</my:other>
	<empty></empty>
</root>

Создание документа

Прежде всего нам нужен объект документа, который содержит все узлы и атрибуты XML как объекты.

DOM

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// namespace !!!
factory.setNamespaceAware(true);
// begin of try - catch block 		
DocumentBuilder docBuilder = factory.newDocumentBuilder();
Document document = docBuilder.parse(is);
Element root = document.getDocumentElement();

JDOM2

SAXBuilder saxBuilder = new SAXBuilder();
// begin of try - catch block 		
Document document = saxBuilder.build(is);
Element root = document.getRootElement();

Как вы можете видеть, DOM нужен еще один шаг кодирования для этой операции. В этом примере я использовал InputStream, но он также может использовать InputSource-Objects, Files и т. Д.

Follownig Исключения должны быть перехвачены:

DOM: ParserConfigurationException, SAXException, IOException, XPathExpressionException, TransformerConfigurationException, TransformerException

JDOM: JDOMException, IOException, TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException

Наибольшее исключение для метода анализа JDOM2 пришло с Transformer, который используется для XSLT-преобразования.

Поиск узлов и атрибутов XML

Другая изящная особенность JDOM над синтаксическим анализатором DOM — получение детей по имени. В следующем примере вы можете увидеть различия в синтаксисе синтаксических анализаторов для этого случая.

DOM

// situation 1 - iterating through child nodes
// searching for the tag-Node
NodeList nodeList = root.getChildNodes();
	for (int i = 0; i < nodeList.getLength(); i++) {
		if (nodeList.item(i).getNodeName().equals("tag")) {
			LOGGER.trace(nodeList.item(i).getNodeName());
		}
	}	
// searching for the other-Node with attribute type - version 1			
	for (int i = 0; i < nodeList.getLength(); i++) {
		if (nodeList.item(i).getNodeName().equals("other")) {
			NamedNodeMap attributes = nodeList.item(i).getAttributes();
			for (int j= 0; j < attributes.getLength(); j++) {
				if ("type".equals(attributes.item(j).getNodeName())) {
					LOGGER.trace("version 1: " + attributes.item(j).getTextContent());
				}
			}
		}
	}
// searching for the other-Node with attribute type - version 2			
	for (int i = 0; i < nodeList.getLength(); i++) {
		if (nodeList.item(i).getNodeName().equals("other")) {
			if (nodeList.item(i).getAttributes().getNamedItem("type") != null) {
				LOGGER.trace("version 2: " + nodeList.item(i).getAttributes().getNamedItem("type").getTextContent());
			}
		}
	}			
			
// any dept level
	NodeList nodeListSomeNode = root.getElementsByTagName("tag");
	for (int i = 0; i < nodeListSomeNode.getLength(); i++) {
		LOGGER.trace("some node: " + nodeListSomeNode.item(i).getNodeName());
	}

JDOM2

// situation 1
// searching for the tag-Node
	for (Element node : root.getChildren()) {
		if (node.getName().equals("tag")) {
			LOGGER.trace(node.getName());
		}
	}
// iterating for the tag-Node			
	for (Element node : root.getChildren("tag")) {
		LOGGER.trace(node.getName());
	}
// searching for the other-Node with attribute type		
	for (Element node : root.getChildren("other")) {
		if (node.getAttribute("type") != null) {
			LOGGER.trace(node.getAttribute("type").getValue());
		}
	}			
// iterating over all nodes			
	for (Content content : root.getDescendants()) {
		LOGGER.trace(content);
	}

Обработка Namspaces

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

DOM

Примечание. Для использования пространства имен в DOM необходимо установить логический флаг DocumentBuilderFactory через метод setNamespaceAware в значение true.

factory.setNamespaceAware(true);
...
// situation 2
// namespaces
	NodeList nodeListNS = root.getChildNodes();
	// node with namespace - any dept level
	nodeListNS = root.getElementsByTagNameNS(namespaceURI, "other");
	for (int i = 0; i < nodeListNS.getLength(); i++) {
		LOGGER.trace("ElemByTName: " + nodeListNS.item(i).getNodeName());
	}
	// node with namespace - any dept level
	nodeListNS = root.getElementsByTagNameNS(namespaceURI, "other");
	for (int i = 0; i < nodeListNS.getLength(); i++) {
		Node node = nodeListNS.item(i).getAttributes().getNamedItemNS(namespaceURI, "type");
		if (node != null) {
			LOGGER.trace(node.getNodeValue());
		}
		LOGGER.trace("ElemByTName Attribute: " + nodeListNS.item(i).getAttributes().getNamedItemNS(namespaceURI, "type"));
	}
	for (int i = 0; i < nodeList.getLength(); i++) {
		if (nodeList.item(i).getNamespaceURI() != null
				&& nodeList
						.item(i)
						.getNamespaceURI()
						.equals(namespaceURI)) {
			String prefix = nodeList.item(i).lookupPrefix(namespaceURI);					
			if (nodeList.item(i).getNodeName().equals(prefix.concat(":").concat("other"))) {
				NamedNodeMap attributes = nodeList.item(i).getAttributes();
				for (int j= 0; j < attributes.getLength(); j++) {
					if (prefix.concat(":").concat("type").equals(attributes.item(j).getNodeName())) {
						LOGGER.trace("end: " + attributes.item(j).getTextContent());
					}
				}
			}
		}
	}

JDOM2

// situation 2
	// namespaces
	Namespace nsMy = root.getNamespace("my"); 
	LOGGER.trace("nsMy: " + nsMy.getPrefix() + " | " + nsMy.getURI());
	LOGGER.trace("other: " + root.getChild("other", nsMy).getTextTrim());
	for (Element node : root.getChildren("other", nsMy)) {
		if (node.getAttribute("type", nsMy) != null) {
			LOGGER.trace(node.getAttribute("type", nsMy).getValue());
		}
	}	

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

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

DOM

// Xpath
	//Evaluate XPath against Document itself
	XPath xPath = XPathFactory.newInstance().newXPath();
	NodeList nodes = (NodeList) xPath.evaluate("/root/deep/tag/other/text()",
			document.getDocumentElement(), XPathConstants.NODESET);
	for (int i = 0; i < nodes.getLength(); ++i) {
		if (nodes.item(i) instanceof Text) {
			System.out.println(((Text)nodes.item(i)).getTextContent());					
		} else if (nodes.item(i) instanceof Element) {
			Element e = (Element) nodes.item(i);
			LOGGER.trace(e.getNodeValue());
		}
	}

JDOM2

XPathFactory xpathFactory = XPathFactory.instance();
String titelTextPath = "root/deep/tag/other/text()";
XPathExpression<Object> expr = xpathFactory.compile(titelTextPath);
List<Object> xPathSearchedNodes = expr.evaluate(document);
for (int i = 0; i < xPathSearchedNodes.size(); i++) {
	Content content = (Content) xPathSearchedNodes.get(i);
	LOGGER.trace(content.getValue());
}

Примечание. Если вы хотите использовать XPath с JDOM2, вам также необходимо использовать библиотеку jaxen.

XSLT

Иногда нам нужно реструктурировать или фильтровать данные XML-файлы. Другой метод , чем при использовании API DOM / JDOM2 для работы узлов , как удалять, создавать узлы и изменять узлы является использование XSLT . XSLT — это другой вид обработки, который я не буду обсуждать здесь, но я хочу показать вам, как XSL-файлы можно использовать через API для объектов документов, созданных анализаторами. Файл XSLT представлен как второй InputStream.

DOM

Transformer transformer2 = TransformerFactory.newInstance()
					.newTransformer(new StreamSource(inputStream2));
	Source source2 = new DOMSource(document);
	Result result2 = new StreamResult(new FileOutputStream(new File(("d:\\test_dom.xml"))));
			transformer2.transform(source2, result2);

JDOM2

Transformer transformer = TransformerFactory.newInstance()
					.newTransformer(new StreamSource(inputStream2));
JDOMSource jdomSource = new JDOMSource(document);
JDOMResult jdomResult = new JDOMResult();
transformer.transform(jdomSource, jdomResult);
xmlOutput.setFormat(Format.getPrettyFormat());
xmlOutput.output(jdomResult.getResult(), new FileOutputStream(new File("d:\\test_jdom.xml")));

Вывести документ

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

DOM

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
DOMSource source = new DOMSource(document);
Result result = new StreamResult(new FileOutputStream(new File(("d:\\test_dom.xml"))));
// output to the System.out
// Result result =  new StreamResult(System.out);
transformer.transform(source, result);

JDOM2

XMLOutputter xmlOutput = new XMLOutputter();
// output to the System.out
//xmlOutput.output(document, System.out);
xmlOutput.output(document, new FileOutputStream(new File("d:\\test_jdom.xml")));
// other possibility
String output = xmlOutput.outputString(document); 

Maven зависимости

Если вы используете maven, вот зависимости, которые я использовал для тестового проекта:

	<dependencies>
		<dependency>
			<groupId>org.jdom</groupId>
			<artifactId>jdom2</artifactId>
			<version>2.0.5</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>1.7.5</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.7.5</version>
		</dependency>
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<dependency>
			<groupId>jaxen</groupId>
			<artifactId>jaxen</artifactId>
			<version>1.1.4</version>
		</dependency>
	</dependencies>

Резюме

Написав эту статью, создав код для нее и выполнив тесты, я также узнал новое о парсерах: 

  • JDOM2 имеет более короткий синтаксис.
  • DOM и JDOM используют один и тот же метод анализа.
  • JDOM2 может создавать документ из основанного на StAX XMLStreamReader.
  • Итерации по узлам также могут быть рекурсивно реализованы обеими библиотеками.
  • Различные методы для обработки пространств имен, как указано выше.
  • Используя пространства имен в DOM, вы должны установить для setNamespaceAware-Flag значение true.
  • Для использования XPath в JDOM2 вам нужна библиотека jaxen
  • Оба парсера используют одни и те же библиотеки для XSLT.

Я также искал ссылки о сравнении производительности, но не нашел ни одного хорошего. Я думаю, что оба парсера должны быть одинаково быстрыми.

Как видите, синтаксис JDOM2 короче для использования той же функциональности. Это одна из причин использования его поверх DOM в новых проектах. Если у вас есть проекты, которые уже используют DOM, то нет необходимости реорганизовывать их только из-за синтаксиса. Наконец, но не в последнюю очередь вы должны следить за лозунгом: никогда не меняйте работающую систему. Или, может быть, мудрее: никогда не запускайте меняющуюся систему.

GitHub

Источники для этого примера можно проверить по адресу : https://github.com/kstojanovski/java-and-xml . Используйте папку java-and-xml и запустите метод test файла TestJavaXml.java из папки test.