groovygrailsxml-namespacesxml-attributexmlslurper

How to add a namespace to an XML attribute using XmlSlurper (Apache Groovy)?


I'm iterating through the child elements of a flattish XML, and adding a certain attribute for elements that are empty.

import groovy.xml.XmlUtil
import groovy.xml.MarkupBuilder
import groovy.xml.XmlSlurper

def xmlTxt = '''<root>
              <node>
                    <element/>
                    <element/>
                  </node>
            </root>'''
          
def xml = new XmlSlurper().parseText(xmlTxt)

xml.node.'*'.each {
    if ( it == "") {
        it.'@foo' = "bar"
    }
}
    
return XmlUtil.serialize(xml)

The result for this is

<?xml version="1.0" encoding="UTF-8"?><root>
  <node>
    <element foo="bar"/>
    <element foo="bar"/>
  </node>
</root>

Now, my real requirement is to change the element so that <element xsi:nil="true"/>, where xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

I've tried using .declareNamespace(xsi:'http://www.w3.org/2001/XMLSchema-instance') when creating the XmlSlurper instance, and then it.'@xsi:nil' = 'true' when adding the attribute, but this results in an error. Is there a quick way to accomplish this with the syntax I'm using?


Solution

  • Here is the best I could come up with:

    import groovy.xml.XmlUtil
    import groovy.xml.MarkupBuilder
    import groovy.xml.XmlSlurper
    
    def xmlTxt = '''
    <root>
        <node>
            <element/>
            <element/>
        </node>
    </root>'''
    
    def parseSlurper( text ) {
        def xml = new XmlSlurper().parseText(text)
    
        xml.node.'*'.replaceNode {
            element('xsi:nil': true, "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance")
        }
        
        // this should work, but doesn't.  But it doesn't throw an error either.
        // xml.node.'*'.each {
        //     it.'@xsi:nil' = true
        //     it.'xmlns:xsi' = "http://www.w3.org/2001/XMLSchema-instance"
        // }
        
        return xml
    }
    
    def parseXmlParser( text ) {
        def xml = new XmlParser().parseText(text)
        xml.node.'*'.each {
            it.replaceNode {
                element('xsi:nil': true, "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance")
            }
        }
        return xml
    }
    
    def xml = parseSlurper( xmlTxt )
    //def xml = parseXmlParser( xmlTxt )
    
    println XmlUtil.serialize(xml)
    

    The issue you were having was that it didn't know about the xsi namespace because it wasn't in your root node definition. Using the declareNamespace wasn't working either since it didn't see that definition. I suspect it was added after the parsing so it couldn't see it. There are some mentions of this in the docs.

    I tried putting the namespace definition directly in the XML:

    def xmlTxt = '''
    <root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <node>
            <element/>
            <element/>
        </node>
    </root>'''
    

    ...but it still didn't seem to see it when I added the @xsi:nil attribute to the element.

    The only thing that worked was replacing the node with a newly defined node that included the xmlns:xsi definition. Otherwise it was "unbounded" and an error was thrown.

    I also checked XmlParser in case it handled namespaces differently, but it was pretty much the same result as XmlSlurper. I included the code should you want to experiment with it too.

    That's about as close as I could get it.