How to get Child elements in Karate framework

I need to extract child elements from response of one api and then pass it dynamically to next api request.

Suppose i have the below xml:

* def foo =
"""
<records>
  <record index="1">a</record>
  <record index="2">b</record>
  <record index="3" foo="bar">c</record>
</records>
"""

I want to extract only this:

  <record index="1">a</record>
  <record index="2">b</record>
  <record index="3" foo="bar">c</record>

I tried below options, but none of then worked:

* def chk = foo//records/*
* def chk = foo//records/child::*
* def chk = foo//records/descendant::*
* print chk

After printing, I get the below, please suggest if i'm missing anything or any other way to do it.Thank you!

13:51:07.046 INFO  - [print] {
  "records": {
    "record": [
      {
        "_": "a",
        "@": {
          "index": "1"
        }
      },
      {
        "_": "b",
        "@": {
          "index": "2"
        }
      },
      {
        "_": "c",
        "@": {
          "foo": "bar",
          "index": "3"
        }
      }
    ]
  }
}

1 answer

  • answered 2018-05-16 09:30 Peter Thomas

    EDIT: looks like a simple string replace would do the trick if all you need to do is take a set of elements and stuff them into another XML template: (I'm leaving the original longer answer at the end of this answer for reference)

    * def foo =
    """
    <records>
      <record index="1">a</record>
      <record index="2">b</record>
      <record index="3" foo="bar">c</record>
    </records>
    """
    * replace foo.<records> = ''
    * replace foo.</records> = ''
    # you can do def bar = read('template.txt') in your case
    * text bar = 
    """
    <after new="namespace">
      @@foo@@
    </after>
    """
    * replace bar.@@foo@@ = foo
    * xml bar = bar
    * match bar ==
    """
    <after new="namespace">
      <record index="1">a</record>
      <record index="2">b</record>
      <record index="3" foo="bar">c</record>
    </after>
    """
    

    Yes, Karate does not support an XML node-list natively, typically you don't need it. And XML has this peculiarity where you always need a "parent" or "wrapping" element.

    As you see above, you do have all the information needed. I'm still not clear what kind of XML you need to pass to the next API - but whatever the case, there is a way in Karate. Here are 2 options. First, iterating over the data and manually emitting XML:

    * def foo =
    """
    <records>
      <record index="1">a</record>
      <record index="2">b</record>
      <record index="3" foo="bar">c</record>
    </records>
    """
    
    * json records = foo.records.record
    * def temp = <bar></bar>
    * def fun = 
    """
    function(r, i){ 
      karate.set('temp', '/bar/record[' + (i+1) + ']', r._); 
      karate.set('temp', '/bar/record[' + (i+1) + ']/@index', r['@'].index); 
    }
    """
    * eval karate.forEach(records, fun)
    * match temp == 
    """
    <bar>
      <record index="1">a</record>
      <record index="2">b</record>
      <record index="3">c</record>
    </bar>
    """
    

    Note that karate.forEach is available only in the upcoming 0.8.0 but you can try 0.8.0.RC3

    Here's another interesting option, if all you need to do is change the XML parent, a string replace will do the trick:

    * replace foo.<records> = '<bar>'
    * replace foo.</records> = '</bar>'
    * xml bar = foo
    * match bar ==
    """
    <bar>
      <record index="1">a</record>
      <record index="2">b</record>
      <record index="3" foo="bar">c</record>
    </bar>
    """