В моей первой статье я упомянул некоторые термины и опубликовал ссылки на 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.