VSe
VSe

Reputation: 929

Generic XQuery to recursively traverse the XML

I am trying to convert the XML input given below to the output format using generic Xquery without calling any parent/child elements. If the parent has two or more recurring child then that should be grouped as an array.

XML:

<root>
<name>xxx</name>
<id>1</id>
<tasks>
    <job>
        <id>1</id>
        <val>2</val>
    </job>
    <job>
        <id>2</id>
        <val>12</val>
    </job>
    <job>
        <id>3</id>
        <val>22</val>
    </job>
</tasks>
</root>

Expected result:

<map>
<string key="name">xxx</string>
<string key="id">1</string>
<array key="tasks">
    <map key="job">
        <string key="id">1</string>
        <string key="val">2</string>
    </map>
    <map key="job">
        <string key="id">2</string>
        <string key="val">12</string>
    </map>
    <map key="job">
        <string key="id">3</string>
        <string key="val">22</string>
    </map>
</array>
</map>

XQuery I tried:

declare default element namespace "http://www.w3.org/2005/xpath-functions";
for $a in /*[1]
let $retval := 
<map>
    {
    for $z in $a//* 
    return if ($z/*/text()) then
    (
    <map>
        {
        for $zz in $z/child::* 
        return if ($zz/text()) then
        (
        <string key="{lower-case(name($zz))}">{data($zz)}</string>
        )
        else ()
        }</map>
    ) 
    else () 
    } 
</map> 
return $retval

Upvotes: 1

Views: 194

Answers (2)

Rupesh_Kr
Rupesh_Kr

Reputation: 3445

You can try this

declare function local:create_structure($child)
{
    for $input in $child
    return
        element {
            (if ($input/*/*) then
                'array'
            else
                if ($input/*) then
                    'map'
                else
                    'string')
        } {
            attribute {'key'} {local-name($input)},
            if ($input/*) then
                local:create_structure($input/*)
            else
                $input/node()
        }
};

let $Output := <map>{local:create_structure(/*)}</map>
return
    $Output

See Transformation at https://xqueryfiddle.liberty-development.net/bFDbxkW/1

Upvotes: 0

Sudeep Rawat
Sudeep Rawat

Reputation: 239

Hi Please test attached code:

declare function local:testChild($in)
{
    if($in/text())
    then <string key="{local-name($in)}">{$in/node()}</string>
    else if(not($in/*/text()))
        then 
   <array key="{local-name($in)}">{for $in in $in/*
                return local:testChild($in)
                }</array>

    else <map key="{local-name($in)}">{for $in in $in/*
                return local:testChild($in)
                }</map>

};

let $markup:=<root>
<name>xxx</name>
<id>1</id>
<tasks>
    <job>
        <id>1</id>
        <val>2</val>
    </job>
    <job>
        <id>2</id>
        <val>12</val>
    </job>
    <job>
        <id>3</id>
        <val>22</val>
    </job>
</tasks>
</root>


let $first:=<map>{for $in in  $markup/*
    return local:testChild($in)
}</map>
return $first

See transformation at https://xqueryfiddle.liberty-development.net/94hwphU

Upvotes: 2

Related Questions