Я создал скрипт PHP, который анализирует XML-файл. Это непросто использовать, и я хотел реализовать его на Java.
Внутри первого элемента присутствуют различные значения wfs:member
элементы элемента I проходят через:
foreach ($data->children("wfs", true)->member as $member) { }
Это было легко сделать с Java:
NodeList wfsMember = doc.getElementsByTagName("wfs:member"); for(int i = 0; i < wfsMember.getLength(); i++) { }
Я открыл XML-файл, как этот
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document doc = documentBuilder.parse(WeatherDatabaseUpdater.class.getResourceAsStream("wfs.xml"));
Затем мне нужно получить атрибут из элемента, называемого observerdProperty
. В PHP это просто:
$member-> children("omso", true)->PointTimeSeriesObservation-> children("om", true)->observedProperty-> attributes("xlink", true)->href
как$member-> children("omso", true)->PointTimeSeriesObservation-> children("om", true)->observedProperty-> attributes("xlink", true)->href
Но на Java, как мне это сделать? Нужно ли использовать getElementsByTagName
и прокручивать их, если я хочу глубже в структуре? `
В PHP весь скрипт выглядит следующим образом.
foreach ($data->children("wfs", true)->member as $member) { $dataType = $dataTypes[(string) $member-> children("omso", true)->PointTimeSeriesObservation-> children("om", true)->observedProperty-> attributes("xlink", true)->href]; foreach ($member-> children("omso", true)->PointTimeSeriesObservation-> children("om", true)->result-> children("wml2", true)->MeasurementTimeseries-> children("wml2", true)->point as $point) { $time = $point->children("wml2", true)->MeasurementTVP->children("wml2", true)->time; $value = $point->children("wml2", true)->MeasurementTVP->children("wml2", true)->value; $data[$dataType][] = array($time, $value) } }
Во втором foreach
я прохожу через элементы наблюдения и получаю от него данные времени и значения. Затем я сохраняю его в массиве. Если мне нужно пропустить элементы в Java, как я описал, это очень сложно реализовать. Я не думаю, что это так, так может кто-нибудь посоветует мне, как реализовать что-то подобное на Java?
Самый простой способ, если производительность не является главной проблемой, вероятно, XPath. С XPath вы можете найти узлы и атрибуты, просто указав путь.
XPathFactory xPathfactory = XPathFactory.newInstance(); XPath xpath = xPathfactory.newXPath(); XPathExpression expr = xpath.compile(<xpath_expression>); NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
Выражение xpath может быть таким же простым, как
"string(//member/observedProperty/@href)"
Для получения дополнительной информации о XPath, учебник XPath из W3Schools довольно хорош.
У вас мало вариантов реализации XML-синтаксического анализа на Java.
Наиболее распространенными являются: DOM, SAX, StAX .
У каждого есть свои плюсы и минусы. С Dom и Sax вы можете проверить свой xml с помощью схемы xsd. Но Stax работает без проверки xsd и намного быстрее.
Например, файл xml :
<?xml version="1.0" encoding="UTF-8"?> <staff xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="oldEmployee.xsd"> <employee> <name>Carl Cracker</name> <salary>75000</salary> <hiredate year="1987" month="12" day="15" /> </employee> <employee> <name>Harry Hacker</name> <salary>50000</salary> <hiredate year="1989" month="10" day="1" /> </employee> <employee> <name>Tony Tester</name> <salary>40000</salary> <hiredate year="1990" month="3" day="15" /> </employee> </staff>
Самый длинный в реализации (на мой взгляд) DOM- парсер:
class DomXmlParser { private Document document; List<Employee> empList = new ArrayList<>(); public SchemaFactory schemaFactory; public final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; public final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; public DomXmlParser() { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(new File(EMPLOYEE_XML.getFilename())); } catch (Exception e) { e.printStackTrace(); } } public List<Employee> parseFromXmlToEmployee() { NodeList nodeList = document.getDocumentElement().getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node node = nodeList.item(i); if (node instanceof Element) { Employee emp = new Employee(); NodeList childNodes = node.getChildNodes(); for (int j = 0; j < childNodes.getLength(); j++) { Node cNode = childNodes.item(j); // identify the child tag of employees if (cNode instanceof Element) { switch (cNode.getNodeName()) { case "name": emp.setName(text(cNode)); break; case "salary": emp.setSalary(Double.parseDouble(text(cNode))); break; case "hiredate": int yearAttr = Integer.parseInt(cNode.getAttributes().getNamedItem("year").getNodeValue()); int monthAttr = Integer.parseInt(cNode.getAttributes().getNamedItem("month").getNodeValue()); int dayAttr = Integer.parseInt(cNode.getAttributes().getNamedItem("day").getNodeValue()); emp.setHireDay(yearAttr, monthAttr - 1, dayAttr); break; } } } empList.add(emp); } } return empList; } private String text(Node cNode) { return cNode.getTextContent().trim(); } }
SAX-синтаксический анализатор:
class SaxHandler extends DefaultHandler { private Stack<String> elementStack = new Stack<>(); private Stack<Object> objectStack = new Stack<>(); public List<Employee> employees = new ArrayList<>(); Employee employee = null; @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { this.elementStack.push(qName); if ("employee".equals(qName)) { employee = new Employee(); this.objectStack.push(employee); this.employees.add(employee); } if("hiredate".equals(qName)) { int yearatt = Integer.parseInt(attributes.getValue("year")); int monthatt = Integer.parseInt(attributes.getValue("month")); int dayatt = Integer.parseInt(attributes.getValue("day")); if (employee != null) { employee.setHireDay(yearatt, monthatt - 1, dayatt) ; } } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { this.elementStack.pop(); if ("employee".equals(qName)) { Object objects = this.objectStack.pop(); } } @Override public void characters(char[] ch, int start, int length) throws SAXException { String value = new String(ch, start, length).trim(); if (value.length() == 0) return; // skip white space if ("name".equals(currentElement())) { employee = (Employee) this.objectStack.peek(); employee.setName(value); } else if ("salary".equals(currentElement()) && "employee".equals(currentParrentElement())) { employee.setSalary(Double.parseDouble(value)); } } private String currentElement() { return this.elementStack.peek(); } private String currentParrentElement() { if (this.elementStack.size() < 2) return null; return this.elementStack.get(this.elementStack.size() - 2); } }
Анализатор Stax:
class StaxXmlParser { private List<Employee> employeeList; private Employee currentEmployee; private String tagContent; private String attrContent; private XMLStreamReader reader; public StaxXmlParser(String filename) { employeeList = null; currentEmployee = null; tagContent = null; try { XMLInputFactory factory = XMLInputFactory.newFactory(); reader = factory.createXMLStreamReader(new FileInputStream(new File(filename))); parseEmployee(); } catch (Exception e) { e.printStackTrace(); } } public List<Employee> parseEmployee() throws XMLStreamException { while (reader.hasNext()) { int event = reader.next(); switch (event) { case XMLStreamConstants.START_ELEMENT: if ("employee".equals(reader.getLocalName())) { currentEmployee = new Employee(); } if ("staff".equals(reader.getLocalName())) { employeeList = new ArrayList<>(); } if ("hiredate".equals(reader.getLocalName())) { int yearAttr = Integer.parseInt(reader.getAttributeValue(null, "year")); int monthAttr = Integer.parseInt(reader.getAttributeValue(null, "month")); int dayAttr = Integer.parseInt(reader.getAttributeValue(null, "day")); currentEmployee.setHireDay(yearAttr, monthAttr - 1, dayAttr); } break; case XMLStreamConstants.CHARACTERS: tagContent = reader.getText().trim(); break; case XMLStreamConstants.ATTRIBUTE: int count = reader.getAttributeCount(); for (int i = 0; i < count; i++) { System.out.printf("count is: %d%n", count); } break; case XMLStreamConstants.END_ELEMENT: switch (reader.getLocalName()) { case "employee": employeeList.add(currentEmployee); break; case "name": currentEmployee.setName(tagContent); break; case "salary": currentEmployee.setSalary(Double.parseDouble(tagContent)); break; } } } return employeeList; } }
И некоторый основной () тест:
public static void main(String[] args) { long startTime, elapsedTime; Main main = new Main(); startTime = System.currentTimeMillis(); main.testSaxParser(); // test elapsedTime = System.currentTimeMillis() - startTime; System.out.println(String.format("Parsing time is: %d ms%n", elapsedTime / 1000)); startTime = System.currentTimeMillis(); main.testStaxParser(); // test elapsedTime = System.currentTimeMillis() - startTime; System.out.println(String.format("Parsing time is: %d ms%n", elapsedTime / 1000)); startTime = System.currentTimeMillis(); main.testDomParser(); // test elapsedTime = System.currentTimeMillis() - startTime; System.out.println(String.format("Parsing time is: %d ms%n", elapsedTime / 1000)); }
Вывод:
Using SAX Parser: ----------------- Employee { name=Carl Cracker, salary=75000.0, hireDay=Tue Dec 15 00:00:00 EET 1987 } Employee { name=Harry Hacker, salary=50000.0, hireDay=Sun Oct 01 00:00:00 EET 1989 } Employee { name=Tony Tester, salary=40000.0, hireDay=Thu Mar 15 00:00:00 EET 1990 } Parsing time is: 106 ms Using StAX Parser: ------------------ Employee { name=Carl Cracker, salary=75000.0, hireDay=Tue Dec 15 00:00:00 EET 1987 } Employee { name=Harry Hacker, salary=50000.0, hireDay=Sun Oct 01 00:00:00 EET 1989 } Employee { name=Tony Tester, salary=40000.0, hireDay=Thu Mar 15 00:00:00 EET 1990 } Parsing time is: 5 ms Using DOM Parser: ----------------- Employee { name=Carl Cracker, salary=75000.0, hireDay=Tue Dec 15 00:00:00 EET 1987 } Employee { name=Harry Hacker, salary=50000.0, hireDay=Sun Oct 01 00:00:00 EET 1989 } Employee { name=Tony Tester, salary=40000.0, hireDay=Thu Mar 15 00:00:00 EET 1990 } Parsing time is: 13 ms
Вы можете увидеть некоторые взгляды на варианты.
Но в java существуют другие как JAXB. Вам нужно иметь схему xsd
и соглашаться с этой схемой, с которой вы генерируете классы. После этого вы можете использовать unmarchal()
для чтения из xml
файла:
public class JaxbDemo { public static void main(String[] args) { try { long startTime = System.currentTimeMillis(); // create jaxb and instantiate marshaller JAXBContext context = JAXBContext.newInstance(Staff.class.getPackage().getName()); FileInputStream in = new FileInputStream(new File(Files.EMPLOYEE_XML.getFilename())); System.out.println("Output from employee XML file"); Unmarshaller um = context.createUnmarshaller(); Staff staff = (Staff) um.unmarshal(in); // print employee list for (Staff.Employee emp : staff.getEmployee()) { System.out.println(emp); } long elapsedTime = System.currentTimeMillis() - startTime; System.out.println(String.format("Parsing time is: %d ms%n", elapsedTime)); } catch (Exception e) { e.printStackTrace(); } } }
Я пробовал этот подход по-прежнему, результат следующий:
Employee { name='Carl Cracker', salary=75000, hiredate=1987-12-15 } } Employee { name='Harry Hacker', salary=50000, hiredate=1989-10-1 } } Employee { name='Tony Tester', salary=40000, hiredate=1990-3-15 } } Parsing time is: 320 ms
Я добавил еще один toString()
, и у него есть другой формат дня приема.
Вот несколько интересных ссылок, которые вам интересны :
Используя парсер DOM
, вы можете легко войти в беспорядок вложенных циклов, как вы уже указывали. Тем не менее структура DOM
представлена Node
содержащим дочерние узлы в виде NodeList
где каждый элемент снова является Node
– это становится идеальным кандидатом для рекурсии .
Чтобы продемонстрировать способность DOM
анализатора дисконтировать размер XML, я взял пример размещенного образца OpenWeatherMap XML.
Поиск по названию города в формате XML
Этот XML
содержит прогноз погоды в Лондоне на каждые 3 часа. Этот XML
делает хороший пример для чтения через относительно большой набор данных и извлечения определенной информации через атрибуты в дочерних элементах.
В снимке мы нацеливаемся на сбор Elements
отмеченных стрелками.
Начнем с создания пользовательского класса для сохранения значений температуры и облаков . Мы бы также переопределили toString()
этого настраиваемого класса, чтобы удобно печатать наши записи.
ForeCast.java
public class ForeCast { /** * Overridden toString() to conveniently print the results */ @Override public String toString() { return "The minimum temperature is: " + getTemperature() + " and the weather overall: " + getClouds(); } public String getTemperature() { return temperature; } public void setTemperature(String temperature) { this.temperature = temperature; } public String getClouds() { return clouds; } public void setClouds(String clouds) { this.clouds = clouds; } private String temperature; private String clouds; }
Теперь к основному классу. В основном классе, где мы выполняем нашу рекурсию, мы хотим создать List
объектов ForeCast
которые хранят отдельные записи температуры и облаков , пройдя весь XML.
// List collection which is would hold all the data parsed through the XML // in the format defined by the custom type 'ForeCast' private static List<ForeCast> forecastList = new ArrayList<>();
В XML родительский элемент как для температуры, так и для облаков – это время , мы будем логически проверять элемент времени.
/** * Logical block */ // As per the XML syntax our 2 fields temperature and clouds come // directly under the Node/Element time if (node.getNodeName().equals("time") && node.getNodeType() == Node.ELEMENT_NODE) { // Instantiate our custom forecast object forecastObj = new ForeCast(); Element timeElement = (Element) node;
После этого мы получили бы информацию о параметрах температуры и облаков, которые могут быть установлены для объекта ForeCast
.
// Get the temperature element by its tag name within the XML (0th // index known) Element tempElement = (Element) timeElement.getElementsByTagName("temperature").item(0); // Minimum temperature value is selectively picked (for proof of concept) forecastObj.setTemperature(tempElement.getAttribute("min")); // Similarly get the clouds element Element cloudElement = (Element) timeElement.getElementsByTagName("clouds").item(0); forecastObj.setClouds(cloudElement.getAttribute("value"));
Полный класс ниже:
CustomDomXmlParser.java
import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; public class CustomDomXmlParser { // List collection which is would hold all the data parsed through the XML // in the format defined by the custom type 'ForeCast' private static List<ForeCast> forecastList = new ArrayList<>(); public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException { // Read XML throuhg a URL (a FileInputStream can be used to pick up an // XML file from the file system) InputStream path = new URL( "http://api.openweathermap.org/data/2.5/forecast?q=London,us&mode=xml") .openStream(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(path); // Call to the recursive method with the parent node traverse(document.getDocumentElement()); // Print the List values collected within the recursive method for (ForeCast forecastObj : forecastList) System.out.println(forecastObj); } /** * * @param node */ public static void traverse(Node node) { // Get the list of Child Nodes immediate to the current node NodeList list = node.getChildNodes(); // Declare our local instance of forecast object ForeCast forecastObj = null; /** * Logical block */ // As per the XML syntax our 2 fields temperature and clouds come // directly under the Node/Element time if (node.getNodeName().equals("time") && node.getNodeType() == Node.ELEMENT_NODE) { // Instantiate our custom forecast object forecastObj = new ForeCast(); Element timeElement = (Element) node; // Get the temperature element by its tag name within the XML (0th // index known) Element tempElement = (Element) timeElement.getElementsByTagName( "temperature").item(0); // Minimum temperature value is selectively picked (for proof of // concept) forecastObj.setTemperature(tempElement.getAttribute("min")); // Similarly get the clouds element Element cloudElement = (Element) timeElement.getElementsByTagName( "clouds").item(0); forecastObj.setClouds(cloudElement.getAttribute("value")); } // Add our foreCastObj if initialized within this recursion, that is if // it traverses the time node within the XML, and not in any other case if (forecastObj != null) forecastList.add(forecastObj); /** * Recursion block */ // Iterate over the next child nodes for (int i = 0; i < list.getLength(); i++) { Node currentNode = list.item(i); // Recursively invoke the method for the current node traverse(currentNode); } } }
Как вы можете понять из приведенного ниже скриншота, мы смогли объединить два конкретных элемента и эффективно присвоить их значения экземпляру Java Collection
. Мы делегировали комплексный синтаксический анализ xml
на общее рекурсивное решение и настраивали главным образом часть logical block
. Как уже упоминалось, это генетическое решение с минимальной настройкой, которое может работать через все допустимые xmls
.
Доступны многие другие альтернативы, вот список парсеров XML с открытым исходным кодом для Java .
Тем не менее, ваш подход с PHP и ваша первоначальная работа с парсером на основе Java выравниваются с решением парсер XML на основе DOM, упрощенным с помощью рекурсии.
API Java, предоставляя вам все, что вам нужно, довольно нелепо использовать, как вы можете видеть. Вы можете проверить Xsylum на что-то более прямолинейное:
(Угадайте, как структурирован ваш XML):
List<XmlElement> elements = Xsylum.elementFor(xmlFile).getAll("wfs:member"); for (XmlElement e : elements) String dataType = e.get("omso").get("om").attribute("xlink");
Как было предложено в другом месте, вы также можете просто использовать XPath для извлечения того, что вам нужно, что также просто с Xsylum:
List<String> values = Xsylum.documentFor(xmlFile).values("//omso/om/@href");
Я бы не предложил вам реализовать свою собственную функцию синтаксического анализа для синтаксического анализа XML, поскольку там уже много опций. Мое предложение – парсер DOM. Вы можете найти несколько примеров в следующей ссылке. (Вы также можете выбрать из других доступных опций)
http://www.javacodegeeks.com/2013/05/parsing-xml-using-dom-sax-and-stax-parser-in-java.html
Вы можете использовать такие команды, как
eElement.getAttribute("id");
Источник: http://www.mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/
Я согласен с тем, что уже было опубликовано о том, что вы сами не выполняете функции синтаксического анализа.
Вместо анализаторов DOM / SAX / STAX я бы предложил использовать JDOM или XOM, которые являются внешними библиотеками.
Связанные дискуссиях:
Чувство моего чувства – это то, что jdom – это тот, который использует большинство разработчиков Java. Некоторые используют dom4j, некоторые xom, некоторые другие, но вряд ли кто-нибудь реализует эти функции синтаксического анализа.
использовать Java startElement и endElement для DOM Parsers