1
\$\begingroup\$

This is the first time I'm parsing an XML file. So I will be grateful for any suggestions on how my code may be improved. Or if you could suggest another approach to solving this problem.

One other question that I have is why attributes 'title' and 'score' have switched in the resulting XML file (xml2.xml)?

I've got an original XML file (xml1.xml) which is parsed via a DOM parser.Then I'm using the DOM tree structure to look through player's child nodes and their attributes, checking if the average score is correct, updating incorrect average scores. Then writing a result to a new XML file (xml2.xml).

The original xml1.xml

<?xml version="1.0" encoding="UTF-8"?>
<game>
    <player nickname="Rooney">
        <challenge title="Mission 1" score="10"></challenge>
        <challenge title="Mission 2" score="5"></challenge>
        <challenge title="Mission 3" score="7"></challenge>
        <average average="256"/>
    </player>

    <player nickname="Doc">
        <challenge title="Mission 1" score="5"></challenge>
        <challenge title="Mission 2" score="2"></challenge>
        <challenge title="Mission 3" score="8"></challenge>
        <average average="5"/>
    </player>

    <player nickname="Junior">
        <challenge title="Mission 1" score="10"></challenge>
        <challenge title="Mission 2" score="3"></challenge>
        <challenge title="Mission 3" score="5"></challenge>
        <average average="6"/>
    </player>

    <player nickname="Candice">
        <challenge title="Mission 1" score="1"></challenge>
        <challenge title="Mission 2" score="7"></challenge>
        <challenge title="Mission 3" score="9"></challenge>
        <average average="6"/>
    </player>
</game>

Result xml2.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?><game>
    <player nickname="Rooney">
        <challenge score="10" title="Mission 1"/>
        <challenge score="5" title="Mission 2"/>
        <challenge score="7" title="Mission 3"/>
        <average average="7,33"/>
    </player>

    <player nickname="Doc">
        <challenge score="5" title="Mission 1"/>
        <challenge score="2" title="Mission 2"/>
        <challenge score="8" title="Mission 3"/>
        <average average="5,00"/>
    </player>

    <player nickname="Junior">
        <challenge score="10" title="Mission 1"/>
        <challenge score="3" title="Mission 2"/>
        <challenge score="5" title="Mission 3"/>
        <average average="6,00"/>
    </player>

    <player nickname="Candice">
        <challenge score="1" title="Mission 1"/>
        <challenge score="7" title="Mission 2"/>
        <challenge score="9" title="Mission 3"/>
        <average average="5,67"/>
    </player>
</game>

Java code

import java.io.*;
import org.w3c.dom.*;

import java.text.DecimalFormat;
import org.xml.sax.SAXException;

import javax.xml.parsers.*;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

public class Parser{

    public static void main(String[] args) throws TransformerConfigurationException, TransformerException {
        try {

            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbf.newDocumentBuilder();
            //Building DOM tree 
            Document doc = dBuilder.parse("C:\\xml1.xml");
            Parser obj = new Parser();
            obj.parseDocument(doc);
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
    }

    public void parseDocument(Document document) throws TransformerConfigurationException, TransformerException {

        //Getting the root element
        Node gameRoot = document.getDocumentElement();

        NodeList playersNodeList = gameRoot.getChildNodes();     
        for (int i = 0; i < playersNodeList.getLength(); i++) {
            Node player = playersNodeList.item(i);           
            if (!(player instanceof Element)) {
                continue;
            }

            double avgScore = 0;
            double sum = 0;
            int scoreCount = 0;

            //Printing out player's nicknames
            NamedNodeMap playerAttributes = player.getAttributes();
            for (int l = 0; l < playerAttributes.getLength(); l++) {
                Attr attr = (Attr) playerAttributes.item(l);
                if ("nickname".equals(attr.getName())) {
                    System.out.println(attr.getValue());
                }                             
            }  
            NodeList playersChildNodes = player.getChildNodes();
            //Iterate through each player's child nodes
            for (int j = 0; j < playersChildNodes.getLength(); j++) { 
                if (!(playersChildNodes.item(j) instanceof Element)) {
                    continue;
                }

                Element element = (Element) playersChildNodes.item(j);
                //Getting attributes from player's child nodes
                NamedNodeMap nnm = element.getAttributes();                  
                for (int a = 0; a < nnm.getLength(); a++) {
                    Attr attr = (Attr) nnm.item(a);

                    //If attribute's name is "score", parse its value to Double and add to the sum
                    if ("score".equals(attr.getName())) {
                        sum += Double.parseDouble(attr.getValue());
                        scoreCount++;                                   
                    }
                    // If attribute's name is "average", count the average score 
                    if ("average".equals(attr.getName())) {
                        avgScore = (scoreCount == 0) ? 0 : sum / scoreCount;
                        DecimalFormat decimalFormat = new DecimalFormat("0.00");
                        System.out.println("Old Average Score Value was: " + attr.getValue() + "." +  " New Average Score Value is: " + avgScore + ".");
                        attr.setValue(decimalFormat.format(avgScore));
                    }    
                }
            }                                 
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            Transformer transformer = transformerFactory.newTransformer();
            DOMSource src = new DOMSource(document);
            StreamResult result = new StreamResult(new File("xml2.xml"));
            transformer.transform(src, result);
        }
    }
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Consider an XSLT solution instead of nested for loops where you parse individual values iteratively down the tree. Sibling to XPath in the Extensible Stylesheet Family, XSLT is designed to transform XML documents to various end use structures. And Java comes built in with the Xalan XSLT 1.0 processor.

Specifically for your needs, XSLT (which is by the way a special, well-formed XML file) can run mathematical operations such as sum() and count() for averages and format for comma-separated decimal numbers.

XSLT Script (save as .xsl file to be loaded in Java code below; can also be embedded Java string)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>

  <xsl:template match="/game">
     <xsl:copy>
        <xsl:apply-templates select="player"/>
     </xsl:copy>
  </xsl:template>

  <xsl:template match="player">
     <xsl:copy>
        <xsl:copy-of select="@*"/>        
        <xsl:copy-of select="challenge"/>
        <xsl:variable name="avg" select="format-number(sum(challenge/@score)
                                                       div count(challenge), '#.00')"/>
        <average>
            <xsl:attribute name="average">
                <xsl:value-of select="translate($avg, '.', ',')"/>
            </xsl:attribute>
        </average>        
     </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Java Code (no for loops used)

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.*;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.OutputKeys;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class PlayerScores {
    public static void main(String[] args) throws IOException, URISyntaxException,
                                                  SAXException, ParserConfigurationException,
                                                  TransformerException {

            // Load XML and XSL Document
            String inputXML = "C:/Path/To/Input.xml";
            String xslFile = "C:/Path/To/XSLT/Script.xsl";
            String outputXML = "C:/Path/To/Output.xml";

            Source xslt = new StreamSource(new File(xslFile));            
            DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();            
            DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
            Document doc = docBuilder.parse (new File(inputXML));

            // XSLT Transformation  with pretty print
            TransformerFactory prettyPrint = TransformerFactory.newInstance();
            Transformer transformer = prettyPrint.newTransformer(xslt);

            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");                        

            // Save Transformed Result to File
            DOMSource source = new DOMSource(doc);
            StreamResult result = new StreamResult(new File(outputXML));        
            transformer.transform(source, result);
    }
}

Output

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<game>
    <player nickname="Rooney">
        <challenge score="10" title="Mission 1"/>
        <challenge score="5" title="Mission 2"/>
        <challenge score="7" title="Mission 3"/>
        <average average="7,33"/>
    </player>
    <player nickname="Doc">
        <challenge score="5" title="Mission 1"/>
        <challenge score="2" title="Mission 2"/>
        <challenge score="8" title="Mission 3"/>
        <average average="5,00"/>
    </player>
    <player nickname="Junior">
        <challenge score="10" title="Mission 1"/>
        <challenge score="3" title="Mission 2"/>
        <challenge score="5" title="Mission 3"/>
        <average average="6,00"/>
    </player>
    <player nickname="Candice">
        <challenge score="1" title="Mission 1"/>
        <challenge score="7" title="Mission 2"/>
        <challenge score="9" title="Mission 3"/>
        <average average="5,67"/>
    </player>
</game>
\$\endgroup\$
2
  • \$\begingroup\$ Thank you! It is a great approach that allows to avoid for loops! \$\endgroup\$
    – Lasamba
    Commented Oct 11, 2016 at 21:34
  • \$\begingroup\$ Fantastic! Glad I could help. \$\endgroup\$
    – Parfait
    Commented Oct 11, 2016 at 22:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.