2

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()
    }
}
Tapas Bose
  • 28,796
  • 74
  • 215
  • 331
  • I think it should be `def locator = 'persons.person.@gender="male"'` . This is a comment and not an answer because, if this is correct, this question should be closed due the issue being a typo. – Michael Easter Dec 09 '17 at 14:04
  • @MichaelEaster, I already have tried the GPath expression, you have provided. It is not raising any error but it is returning an element of type `java.lang.String`. The expression should evaluate an element of type `groovy.util.slurpersupport.NodeChild` – Tapas Bose Dec 09 '17 at 14:49
  • Aren't you mixing XPath and GPath? http://groovy-lang.org/processing-xml.html#_gpath . Also have a look at https://stackoverflow.com/questions/2268006/how-do-i-create-an-xpath-function-in-groovy – Tarun Lalwani Dec 12 '17 at 15:45
  • @TarunLalwani, GPath is the Groovy way of XPath, isn't it? I need to work with GPath not XPath. – Tapas Bose Dec 12 '17 at 15:48
  • I believe they are not. You should verify that by first leaving the dynamic eval and getting the code working directly. Can you do that? If it doesn't work for you with this xpath without eval, then it can't work with eval also – Tarun Lalwani Dec 12 '17 at 15:57
  • @TarunLalwani, I already have tried without `Eval`, and that didn't work either. I was hoping that, I'm doing some mistake defining the GPath expression. – Tapas Bose Dec 12 '17 at 15:59
  • If you read the link I posted for GPath, you will find that it is similar to XPath syntax but it is not the same. – Tarun Lalwani Dec 12 '17 at 16:00
  • @TapasBose Have you got chance to try the other solution? – Rao Dec 16 '17 at 15:01
  • @Rao, your solution worked like a charm. – Tapas Bose Dec 18 '17 at 14:52
  • @TapasBose Glad that you found it helpful. Appreciate if you can upvote the helpful answer. – Rao Dec 18 '17 at 16:49
  • @Rao, I must have missed to give it a upvote. I was in hurry to give you the bounty ☺️ – Tapas Bose Dec 18 '17 at 16:51

2 Answers2

3

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)
Rao
  • 20,781
  • 11
  • 57
  • 77
0

Think you need

def locator = "persons.person.findAll { it.@gender == 'male' }"

I'd avoid this if at all possible, using Eval isn't a safe path to walk

Rao
  • 20,781
  • 11
  • 57
  • 77
tim_yates
  • 167,322
  • 27
  • 342
  • 338
  • Hi, I have taken the idea from your answer: https://stackoverflow.com/a/8243708/576758. Also, as I have mentioned in my question, I need a way to find element's existence using dynamic GPath. I cannot have code specific to a particular XML. Moreover why `Eval` is not safe? – Tapas Bose Dec 09 '17 at 14:42
  • 1
    I assume as you want it dynamic, it will be entered by some user at run time. What happens if they enter `System.exit(0)`? Can you think of anything worse they can enter? – tim_yates Dec 09 '17 at 15:24
  • Not a chance. Since the utility method will be used extensively from various parts of the application wrien by our team, and I cannot afford to write duplicate code in various places, for maintenability. It's not an API. And if someone wants to drink poison willingly, what can we do ;) – Tapas Bose Dec 09 '17 at 15:31
  • Yeah, but you're storing poison in the fridge. In a milk bottle. Someone's going to get hurt... – tim_yates Dec 09 '17 at 16:15
  • I'm the safekeeper, hence one hundred percent guarantee, no one will get hurt. – Tapas Bose Dec 09 '17 at 16:47
  • What is result you try the solution (exclude `Eval` suggestion) ? – Rao Dec 09 '17 at 23:09
  • @Rao, I have provided new information to the original question. Please check. – Tapas Bose Dec 10 '17 at 08:44
  • Couple of things based your comment & edit. 1. Do not find any more useful info based on the edit other than original post. 2. It does not confirm if the above solution has been tried and whether it works or not. – Rao Dec 10 '17 at 11:59
  • @Rao, of course, the solution was tried. The original post contains the same code which was given in the update. I update was given merely to give an idea what I have actually implemented, hoping that it could explain more. – Tapas Bose Dec 12 '17 at 15:10
  • 2
    Have you seen https://stackoverflow.com/questions/2268006/how-do-i-create-an-xpath-function-in-groovy ? – tim_yates Dec 12 '17 at 15:33
  • 1
    @tim_yates, there is typo. it is supposed to be `persons.person`. `s` is missing for the prior one. Just edited the answer. Hope you don't mind. May be that is why OP did not get the desired result. – Rao Dec 13 '17 at 08:02
  • 1
    Initially I could not understand why OP did not say what happened when he tried. So I tried it as is, then I realized. – Rao Dec 13 '17 at 08:22
  • @TapasBose, have you got chance to try after the edit? – Rao Dec 15 '17 at 10:27
  • @Rao, I just tested it and it worked. How to use this `locator` on the `GPathResult` of the `XmlSlurper#parseText(text)` without using `Eval`? I tried `new XmlSlurper().parseText(xml)."${locator}"` but that returned empty, whereas `Eval.x(new XmlSlurper().parseText(xml), "x.${locator}")` has returned "Z". – Tapas Bose Dec 15 '17 at 14:00
  • That was no where in the question or so far. And that is what suggested by @tim_yates and you were desperate to use Eval till now. – Rao Dec 15 '17 at 14:17
  • @TapasBose, please see if the below solution to suit you need. – Rao Dec 15 '17 at 14:38