Search code examples
javaxmlswingdomjcombobox

xml DOM- Newline adds the text/attribute content twice in java DOM parser


I am working on parsing an XML file. I developed a swing application that reads data from xml file. It adds the chapters in comboBxCh based on the subject value set in comboBxSb. The code is below:

Java code:

public void loadChapters(JComboBox comboBxCh, JComboBox comboBxSb)
{
    tempList = xmlDoc.getElementsByTagName("subject");
    NodeList secNodeList = null;
    Element ele=null;
    int i, j, totalElements;
    totalElements = tempList.getLength();
    i = j =0;
    for(i=0;i<totalElements;i++)
    {
        secNodeList = tempList.item(i).getChildNodes();
        for(j=0;j<secNodeList.getLength();j++)
        {
            if(secNodeList.item(j).getNodeType()==Node.ELEMENT_NODE)
            {   
                ele = (Element)secNodeList.item(j);
            }

            if(ele!=null && ele.getTagName()=="sname" && ele.getTextContent()==comboBxSb.getSelectedItem().toString() )
            {
                for(i=0;i<secNodeList.getLength();i++)
                {
                    if(secNodeList.item(i).getNodeType()==Node.ELEMENT_NODE)
                        ele = (Element)secNodeList.item(i);
                    if(ele.getTagName()=="chapter") //Adding it solves my problem-> &&i%2==1
                    {
                            comboBxCh.addItem(ele.getAttribute("name"));
                    }
                }
                return;
            }
        }
    }
}

XML Code:

<subjects>
    <subject id="1">
        <sname>Quantitave Aptitude</sname>
        <chapter name="Number series"/>
        <chapter name="Time and work"/>
        <chapter name="Trains and rivers"/>
    </subject>
    <subject id="2">
        <sname>English</sname>
        <chapter name="Articles"/>
        <chapter name="Nouns"/>
        <chapter name="Comprehension text"/>
    </subject>
    <subject id="3">
        <sname>Logical Reasoning</sname>
        <chapter name="What's next"/>
        <chapter name="Next figure"/>
        <chapter name="Series"/>
    </subject>
    <subject id="4">
        <sname>GK</sname>
    </subject>
</subjects>

When swing application loads then comboBxSb contains all four subjects and Quantitative Aptitude (with id="1") remains selected in beginning hence its three chapters load in the comboBxCh. But its each chapter loads twice! In xml file if I remove the \n from between chapter names in each line then repetition doesn't happen. That is ->

    <subject id="1">
        <sname>Quantitave Aptitude</sname>
        <chapter name="Number series"/><chapter name="Time and work"/><chapter name="Trains and rivers"/>
    </subject>

. /-------------------------------------/

There is a screenshot of the application:

Chapter names repeating twice :-/

The solution to this, without modifying XML, is already mention in comment. That's I can add only odd(or even) no. of chapters. But my question is Why is this happening? It also happens if I put the names of chapters as text content between <chapter> </chapter> tags and the extracting text with 'getTextContent()`


Solution

  • It's hard to be 100% sure, as you've only provided an out-of-context code snippet, but my "guess" is you've not cleared the JComboBox of any previous values.

    Having said that though, your loadChapters method could be better done. Instead, you could make use of the xpath API to query the XML document and return only those values you actually want, like you might do for a database, for example...

    public static List<String> loadChapters(Document xmlDoc, String name) throws XPathExpressionException {
        List<String> results = new ArrayList<>(25);
        XPathFactory xf = XPathFactory.newInstance();
        XPath xPath = xf.newXPath();
    
        String query = "/subjects/subject[sname[text()='" + name + "']]/chapter/@name";
        XPathExpression xExp = xPath.compile(query);
        NodeList nl = (NodeList) xExp.evaluate(xmlDoc, XPathConstants.NODESET);
        for (int index = 0; index < nl.getLength(); index++) {
            Node node = nl.item(index);
            results.add(node.getNodeValue());
        }
        return results;
    }
    

    Using something like...

    try {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        Document doc = dbf.newDocumentBuilder().parse(new File("Test.xml"));
        List<String> chapters = loadChapters(doc, "Quantitave Aptitude");
        for (String chapter : chapters) {
            System.out.println(chapter);
        }
    } catch (ParserConfigurationException | SAXException | IOException | XPathExpressionException exp) {
        exp.printStackTrace();
    }
    

    this prints...

    Number series
    Time and work
    Trains and rivers
    

    so we can ensure that the loadChapters method is not returning duplicates.

    Now to make your life easier, you could create a custom ComboBoxModel which is backed by a List....

    public class ListComboBoxModel<E> extends AbstractListModel<E> implements MutableComboBoxModel<E> {
    
        private List<E> values;
        private Object selectedItem;
    
        public ListComboBoxModel() {
            this.values = new ArrayList<>(25);
        }
    
        public ListComboBoxModel(List<E> values) {
            this.values = new ArrayList<>(values);
        }
    
        @Override
        public int getSize() {
            return values.size();
        }
    
        @Override
        public E getElementAt(int index) {
            return values.get(index);
        }
    
        @Override
        public void addElement(E item) {
            values.add(item);
        }
    
        @Override
        public void removeElement(Object obj) {
            values.remove((E)obj);
        }
    
        @Override
        public void insertElementAt(E item, int index) {
            values.add(index, item);
        }
    
        @Override
        public void removeElementAt(int index) {
            values.remove(index);
        }
    
        @Override
        public void setSelectedItem(Object anItem) {
            selectedItem = anItem;
        }
    
        @Override
        public Object getSelectedItem() {
            return selectedItem;
        }
    
    }
    

    So you can create a new using something like...

    ComboBoxModel<String> model = new ListComboBoxModel<String>(loadChapters(doc, "Quantitave Aptitude"));
    

    and then apply the new model directly to the JComboBox