Search code examples
javaxmlfor-loopxmlreaderdomparser

Java for loop to obtain child elements from xml document


I'm getting hard time in figuring how to write a loop to retrieve values from a given XML document for simplicity I'll give you a sample of the xml file

</solution>
          <solution>
               <cost>505.9208295302417</cost>
               <routes>
                    <route>
                         <driverId>noDriver</driverId>
                         <vehicleId>1_1</vehicleId>
                         <start>0.0</start>
                         <act type="service">
                              <serviceId>5  </serviceId>
                              <arrTime>109.9819741964403</arrTime>
                              <endTime>119.9819741964403</endTime>
                         </act>
                         <end>229.9639483928806</end>
                    </route>
                    <route>
                         <driverId>noDriver</driverId>
                         <vehicleId>3_1</vehicleId>
                         <start>0.0</start>
                         <act type="service">
                              <serviceId>4  </serviceId>
                              <arrTime>109.98190391287031</arrTime>
                              <endTime>119.98190391287031</endTime>
                         </act>
                         <act type="service">
                              <serviceId>2 </serviceId>
                              <arrTime>119.98282618841856</arrTime>
                              <endTime>129.98282618841856</endTime>
                         </act>
                         <act type="service">
                              <serviceId>1 </serviceId>
                              <arrTime>129.98372097890456</arrTime>
                              <endTime>139.98372097890456</endTime>
                         </act>
                         <act type="service">
                              <serviceId>3 </serviceId>
                              <arrTime>139.9846432544528</arrTime>
                              <endTime>149.9846432544528</endTime>
                         </act>
                         <end>259.9668316441239</end>
                    </route>
               </routes>
          </solution>
     </solutions>

so basically what I've in the code that I'll be showing you is to obtain value from only the node where there is cost = 505.9208295302417, please don't take into account this part of the code, so the next step was to retrieve driverid``, vehicleid and act , I know that there is a missing for loop but I don't know where to put it!! please can someone help. The desired output that i need to have is like for every vehicleid obtain list act values associated with it

java code

        public static void main(String[] args) {
        try {
            int totalVehicle;
            totalVehicle = 2;
            File fXmlFile = new File("C:/Users/HP/Desktop/solution.xml");
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(fXmlFile);
            doc.getDocumentElement().normalize();
            Double requiredCost = 505.9208295302417;
            System.out.println("Root element :" + doc.getDocumentElement().getNodeName());
            // NodeList nList = doc.getElementsByTagName("route");
            System.out.println("----------------------------");
            NodeList nodeList = doc.getElementsByTagName("solution");
            for (int i = 0; i < nodeList.getLength(); i++) {

                Node solutionNode = nodeList.item(i);

                if (solutionNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element solutionElement = (Element) solutionNode;
                    Node costNode = solutionElement.getElementsByTagName("cost").item(0);
                    Node route = solutionElement.getElementsByTagName("routes").item(0);
                    // if correct cost, proceed to parse further
                    Double costValue = Double.valueOf(costNode.getTextContent());
                    if (Double.compare(requiredCost, costValue) == 0) {
                        System.out.println("working");
                        // there you go, found the node with the cost 505.9208295302417
                        // now just parse all the node elements you need here

                        System.out.println("cost : "
                                + solutionElement.getElementsByTagName("cost")
                                        .item(0).getTextContent());
                        for (int h = 0; h < totalVehicle; h++) {
                            System.out.println("DriverId : "
                                    + solutionElement.getElementsByTagName("driverId")
                                            .item(h).getTextContent().toString());
                            System.out.println("vehicleId : "
                                    + solutionElement.getElementsByTagName("vehicleId")
                                            .item(h).getTextContent());
NodeList optionList = solutionElement.getElementsByTagName("act");
                            System.out.println(optionList.getLength());

        for (int j = 0; j < optionList.getLength(); ++j)
        {

            for(int k =0;k<1;++k){
                Element option = (Element) optionList.item(j);
            String optionText = option.getTextContent();
           //address.add(optionText.replaceAll("[^A-Za-z]"," "));
            System.out.println("Citizen :"+optionText.replaceAll("[^A-Za-z]"," "));}
            ;


        }

Solution

  • As others have suggested, using xpath would be much easier but if it's an absolute requirement that you loop over all this stuff, break the problem into smaller, more manageable pieces. I thought I'd give it a try and I have a complete, working solution to your problem.

    The idea is to break xml up into Java objects and use those objects to do the work you need.

    public static void main(String... args) throws SAXException, IOException, ParserConfigurationException {
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                .parse(new File("/home/william/Documents/test.xml"));
        doc.getDocumentElement().normalize();
    
        NodeList nodeList = doc.getElementsByTagName("solution");
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node solutionNode = nodeList.item(i);
    
            try {
                System.out.println(Solution.newInstance(solutionNode).toString());
            } catch (Exception e) {
                // do something
                e.printStackTrace();
            }   
        }
    }
    

    Here, your original document parsed the same way you did it but Solution is its own class you can put the logic in:

    public class Solution {
    
        private final double cost;
    
        private final Collection<Route> routes;
    
        public static final Solution newInstance(Node solution) throws Exception {
            return new Solution(solution);
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("solution:");
            sb.append(System.lineSeparator());
            sb.append("--------");
            sb.append(System.lineSeparator());
            sb.append("cost:");
            sb.append(this.cost);
    
            for (Route route : this.routes) {
                sb.append(route.toString());
            }
    
            return sb.toString();
        }
    
        private Solution(Node solution) throws Exception {
            if (solution.getNodeType() == Node.ELEMENT_NODE) {
                Element solutionElement = (Element) solution;
                this.cost = Double.valueOf(solutionElement.getElementsByTagName("cost").item(0).getTextContent());
                this.routes = Routes.get(solutionElement.getElementsByTagName("routes").item(0));
            } else {
                // TODO something?
                this.cost = 0D;
                this.routes = Collections.emptyList();
            }
        }
    }
    

    Note that in your main method you have a check on the cost. I've left all class variable getter methods out, add the ones you need if you need to read them from outside the object. So for example in your Solution class add a getCost() method and then in the main method you can check as appropriate: if (solution.getCost() == requiredCost) { ...

    Here, based on your requirements, you could add getters to the class members if needed. I've just overridden the toString() method in order to print something you can read.

    Routes is a util class to create a collection of Route objects out of the child nodes of the <routes> node.

    public class Routes {
    
        private final Collection<Route> items;
    
        public static Collection<Route> get(Node routes) throws Exception {
            return new Routes(routes).items;
        }
    
        private Routes() {
            this.items = new ArrayList<>();
        }
    
        private Routes(Node routes) throws Exception {
            this.items = new ArrayList<>();
            NodeList routesList = routes.getChildNodes();
            for (int i = 0; i < routesList.getLength(); i++) {
                Node route = routesList.item(i);
                if (Node.ELEMENT_NODE == route.getNodeType()) {
                    items.add(Route.newInstance(route));
                } else {
                    // TODO something?
                }
            }
        }   
    }
    

    The Acts class has similar logic to the Routes class (same thing but for acts):

    public class Acts {
    
        private Collection<Act> items;
    
        public static Collection<Act> get(NodeList acts) throws Exception {
            return new Acts(acts).items;
        }
    
        private Acts() {
            this.items = new ArrayList<>();
        }
    
        private Acts(NodeList acts) throws Exception {
            this.items = new ArrayList<>();
            for (int i = 0; i < acts.getLength(); i++) {
                Node act = acts.item(i);
                if (Node.ELEMENT_NODE == act.getNodeType()) {
                    this.items.add(Act.newInstance(act));
                } else {
                    // TODO something?
                }
            }
        }
    }
    

    and finally, each individual Act:

    public class Act {
    
        private final String service;
    
        private final double arrTime;
    
        private final double endTime;
    
        public static Act newInstance(Node act) throws Exception {
            return new Act(act);
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
    
            sb.append("Act");
            sb.append(System.lineSeparator());
            sb.append("arr time");
            sb.append(System.lineSeparator());
            sb.append(this.arrTime);
            sb.append(System.lineSeparator());
            sb.append("end time:");
            sb.append(System.lineSeparator());
            sb.append(this.endTime);
            sb.append(System.lineSeparator());
            sb.append("service:");
            sb.append(System.lineSeparator());
            sb.append(this.service);
    
            return sb.toString();
        }
    
        private Act(Node act) throws Exception {
            Element actElement = (Element) act;
            this.service = actElement.getAttribute("service");
            this.arrTime = Double.valueOf(actElement.getElementsByTagName("arrTime").item(0).getTextContent());
            this.endTime = Double.valueOf(actElement.getElementsByTagName("endTime").item(0).getTextContent());
        }
    }