3

I have made a PHP script that parses an XML file. This is not easy to use and I wanted to implement it in Java.

Inside the first element there are various count of wfs:member elements I loop through:

foreach ($data->children("wfs", true)->member as $member) { }

This was easy to do with Java:

NodeList wfsMember = doc.getElementsByTagName("wfs:member");
for(int i = 0; i < wfsMember.getLength(); i++) { }

I have opened the XML file like this

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document doc = documentBuilder.parse(WeatherDatabaseUpdater.class.getResourceAsStream("wfs.xml"));

Then I need to get a attribute from an element called observerdProperty. In PHP this is simple:

$member->
    children("omso", true)->PointTimeSeriesObservation->
    children("om", true)->observedProperty->
    attributes("xlink", true)->href

But in Java, how do I do this? Do I need to use getElementsByTagName and loop through them if I want to go deeper in the structure?`

In PHP the whole script looks the following.

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)
    }
}

In the second foreach I loop through the observation elements and get the time and value data from it. Then I save it in an array. If I need to loop through the elements in Java the way I described, this is very hard to implement. I don't think that is the case, so could someone advice me how to implement something similar in Java?

MikkoP
  • 4,864
  • 16
  • 58
  • 106

6 Answers6

6

The easiest way, if performance is not a main concern, is probably XPath. With XPath, you can find nodes and attributes simply by specifying a path.

XPathFactory xPathfactory = XPathFactory.newInstance();
XPath xpath = xPathfactory.newXPath();
XPathExpression expr = xpath.compile(<xpath_expression>);
NodeList nl = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);

The xpath_expression could be as simple as

"string(//member/observedProperty/@href)"

For more information about XPath, XPath Tutorial from W3Schools is pretty good.

Adrian Pang
  • 1,125
  • 6
  • 12
4

You have few variations how to implement XML parsing at Java.

The most common is: DOM, SAX, StAX.

Everyone one has pros and cons. With Dom and Sax you able to validate your xml with xsd schema. But Stax works without xsd validation, and much faster.

For example, xml file:

<?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>

The longest at implementation (to my mind) DOM parser:

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 parser:

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 parser:

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;
    }    
}

And some main() test:

 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));
}

Output:

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

You can see some glimpse view at there variations.

But at java exist other as JAXB - You need to have xsd schema and accord to this schema you generate classes. After this you this can use unmarchal() to read from xml file:

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();
        }
    }
}

I tried this one approach as before, result is next:

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

I added another toString(), and it has different hire day format.

Here is few links that is interesting for you:

catch23
  • 17,519
  • 42
  • 144
  • 217
  • Thanks for your long and detailed answer! It's nice you compared the options but I decided to award the bounty for the simplest solution here. Don't worry, I upvoted you too! – MikkoP Feb 18 '14 at 12:14
3

DOM Parser through Recursion

Using a DOM parser, you can easily get into a mess of nested for loops as you've already pointed out. Nevertheless, DOM structure is represented by Node containing child nodes collection in the form of a NodeList where each element is again a Node - this becomes a perfect candidate for recursion.

Sample XML

To showcase the ability of DOM parser discounting the size of the XML, I took the example of a hosted sample OpenWeatherMap XML.

Searching by city name in XML format

This XML contains London's weather forecast for every 3 hour duration. This XML makes a good case of reading through a relatively large data set and extracting specific information through attributes within the child elements.

enter image description here

In the snapshot, we are targeting to gather the Elements marked by the arrows.

The Code

We start of by creating a Custom class to hold temperature and clouds values. We would also override toString() of this custom class to conveniently print our records.

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;
}

Now to the main class. In the main class where we perform our recursion, we want to create a List of ForeCast objects which store individual temperature and clouds records by traversing the entire 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<>();

In the XML the parent to both temperature and clouds elements is time, we would logically check for the time element.

/**
 * 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;

Thereafter, we would get a handle on temperature and clouds elements which can be set to the ForeCast object.

    // 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"));

The complete class below:

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);

        }

    }
}

The Output

As you can figure out from the screenshot below, we were able to group together the 2 specific elements and assign their values effectively to a Java Collection instance. We delegated the complex parsing of the xml to the generic recursive solution and customized mainly the logical block part. As mentioned, it is a genetic solution with a minimal customization which can work through all valid xmls.

enter image description here

Alternatives

Many other alternatives are available, here is a list of open source XML parsers for Java.

However, your approach with PHP and your initial work with Java based parser aligns to the DOM based XML parser solution, simplified by the use of recursion.

StoopidDonut
  • 8,547
  • 2
  • 33
  • 51
  • Thanks for this one too. Though I decided not to go with recursion because there seems to be easier alternatives too. – MikkoP Feb 18 '14 at 12:16
0

I wouldn't suggest you to implement your own parse function for XML parsing since there are already many options out there. My suggestion is DOM parser. You can find few examples in the following link. (You can also choose from other available options)

http://www.javacodegeeks.com/2013/05/parsing-xml-using-dom-sax-and-stax-parser-in-java.html

You can use commands such as

eElement.getAttribute("id");

Source: http://www.mkyong.com/java/how-to-read-xml-file-in-java-dom-parser/

iordanis
  • 1,284
  • 2
  • 15
  • 28
0

I agree what has been already posted about not implementing parse functions yourself.

Instead of DOM/SAX/STAX parsers though, I would suggest using JDOM or XOM, which are external libraries.

Related discussions:

My gut feeling is that jdom is the one most java developers use. Some use dom4j, some xom, some others, but hardly anybody implements these parsing functions themselves.

Community
  • 1
  • 1
eis
  • 51,991
  • 13
  • 150
  • 199
0

use Java startElement and endElement for DOM Parsers

AshuKingSharma
  • 757
  • 7
  • 20