Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
1.7.5
-
None
-
JDK 6
-
Patch
Description
When using XMLSlurper to parse XML in which there is an element with two attributes having the same name but belonging to different namespaces, one of the attribute values will be unaccessible.
This is due to XMLSlurper using the local attribute name as a key to a map in which data about the attributes are inserted and thus a key-collision will occur.
Adding the following method to the groovy.util.XmlSlurperTest in the Groovy source-code will expose the problem:
void testSameNameAttributes() { def theInputData = """ <RootElement xmlns="http://www.ivan.com/ns1" xmlns:two="http://www.ivan.com/ns2"> <ChildElement ItemId="FirstItemId" two:ItemId="SecondItemId">Child element data</ChildElement> </RootElement>""" def theXml = new MyXmlSlurper().parseText(theInputData).declareNamespace(one:"http://www.ivan.com/ns1", two: "http://www.ivan.com/ns2") assert theXml.ChildElement.@'ItemId' == "FirstItemId" assert theXml.ChildElement.@'two:ItemId' == "SecondItemId" }
The following patches will make the above test pass, but I am not entirely sure it is the best solution:
Patching the XmlSlurper.startElement method:
public void startElement(final String namespaceURI, final String localName, final String qName, final Attributes atts) throws SAXException { addCdata(); final Map attributes = new HashMap(); final Map attributeNamespaces = new HashMap(); for (int i = atts.getLength() - 1; i != -1; i--) { if (atts.getURI(i).length() == 0) { attributes.put(atts.getQName(i), atts.getValue(i)); } else { // PATCHED START - Use fully qualified attribute names instead of just local attribute names attributes.put(atts.getQName(i), atts.getValue(i)); attributeNamespaces.put(atts.getQName(i), atts.getURI(i)); // PATCH END } } final Node newElement; ...
Patching groovy.util.slurpersupport.Attributes, method iterator() to use namespace-qualified attribute names when looking up attribute value. This is the patch I am unsure whether it is the best solution.
public Iterator iterator() { return new NodeIterator(nodeIterator()) { protected Object getNextNode(final Iterator iter) { while (iter.hasNext()) { final Object next = iter.next(); if (next instanceof Attribute) { return next; } else { // PATCH START - Added namespace prefix when looking up attribute. String attributeKey = ""; if (Attributes.this.namespacePrefix != null && !"*".equals(Attributes.this.namespacePrefix) && Attributes.this.namespacePrefix.length() > 0) { attributeKey = Attributes.this.namespacePrefix + ":"; } attributeKey += Attributes.this.attributeName; final String value = (String) ((Node) next).attributes().get(attributeKey); // PATCH END if (value != null) { return new Attribute(Attributes.this.attributeName, value, new NodeChild((Node) next, Attributes.this.parent.parent, "", Attributes.this.namespaceTagHints), "", Attributes.this.namespaceTagHints); } } } return null; } }; }