xmlgroovygpath

Find XML Element by attribute value using dynamic GPath Expression


Following is a sample XML:

<root>
    <persons>
        <person gender="female">X</person>
        <person gender="female">Y</person>
        <person gender="male">Z</person>
    </persons>
</root> 

I want to get the element count which has gender="male" by using GPath.

I have following code:

def xml =
'''
    <root>
        <persons>
            <person gender="female">X</person>
            <person gender="female">Y</person>
            <person gender="male">Z</person>
        </persons>
    </root>    
'''

def slurper = new XmlSlurper()
def parsedText = slurper.parseText(xml)
def locator = 'persons.person[@gender="male"]'

def elements = Eval.x(parsedText, "x.${locator}")

println elements.size()

It is giving me error:

org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
Script1.groovy: 1: unexpected token: = @ line 1, column 25.
   x.persons.person[@gender="male"]
                           ^

1 error

The code is only for demonstration purpose of the problem. In the actual case, I have a utility method which accepts an XML and a GPath and returns if there is any element can be found by the given GPath.

Update: 1

Following is the actual utility method which takes xml and locator (GPath) as args and check if there is any element whose path matches the provided locator.

public static void verifyElementExists(String xml, String locator) throws NoElementFoundException {
    def slurper = new XmlSlurper()
    def parsedText = slurper.parseText(xml)
    def elements = Eval.x(parsedText, "x.${locator}")

    if(elements.size() == 0) {
        throw new NoElementFoundException()
    }
}

Solution

  • Not sure of the exact use case you are trying to achieve.

    In case if you require a method which can return if certain element or provided GPath is available or not, it can be achieved as below:

    Solution really doesn't change much though.

    Instead of string as locator, user needs to pass the same thing as Closure and it does not use Eval.

    //Method to find if element exists.
    static def isElementExists (String xmlStr, Closure closure){
        def xml = new XmlSlurper().parseText(xmlStr)
        def elements = closure(xml)
        elements.size() ? true : false    
    }
    
    def xmlString = '''<root>
        <persons>
            <person gender="female">X</person>
            <person gender="female">Y</person>
            <person gender="male">Z</person>
        </persons>
    </root>'''
    
    //Call to the above method, use either of the way to make a call; both are same
    //In the below example, the same string used as locator is being passed as Closre
    println isElementExists(xmlString) {x -> x.'**'.findAll { it.name()=='person' && it.@gender == 'male' } }
    

    or

    println isElementExists(xmlString, {x -> x.'**'.findAll { it.name()=='person' && it.@gender == 'male' } } )
    

    If you want to just check person element present, then you can use it as below:

    println isElementExists(xmlString) {x -> x.persons.person }
    

    You may quickly try it online demo

    EDIT: Improving the readability or simplifying.

    You may even do as below. Of course, the last statement and below statements result the same.

    def locator = {x -> x.persons.person }
    println isElementExists(xmlString, locator)