How to use XPath to select Cartesian Product set of non null sibling nodes?

473 views Asked by At

First time poster here, so please don't hesitate to let me know if I've done something wrong.

I'm using Altova Stylevision to print out form documents, and I need to print a copy of the form for each unique set of two node collections. Below is a sample XML to better explain what I'm asking for.

<form>
    <Animals>
        <Animal>
            <AName>Boomer</AName>
            <AAddress>House</AAddress>
        </Animal>
        <Animal>
            <AName>Growl</AName>
            <AAddress>Street</AAddress>
        </Animal>
        <Animal>
            <AName>Incognito</AName>
            <AAddress>Nowhere</AAddress>
        </Animal>
    </Animals>
    <People>
        <Person>
            <PName>Willy</PName>
            <PAddress>123 Common Lane</PAddress>
        </Person>
        <Person>
            <PName>Wonka</PName>
            <PAddress>Chocolate Factory</PAddress>
        </Person>
    </People>
</form>

I'd like to produce 6 unique values (the Cartesian Product) from the above sample, containing every combination of <Animal> and <Person> where neither of them are null. So, the output for this example would be like (just showing the grouping, need all child nodes instead of just names):

([Boomer, Willy], [Boomer, Wonka], [Growl, Willy], [Growl, Wonka], [Incognito, Willy], [Incognito, Wonka])

EDIT: I'm trying to get the nodes, not just the <Name> element value. Because I'm wrapping the whole StyleVision form in this template, the scope should be at the <Animal> and <Person> level. This way, in the form I can just say /AName, /PName, and get one value for /AName and /PName on each copy of the form. It should act as 6 instances of an element node, each one representing a unique combination of both <Person> and <Animal>.

I looked into the for # in # return syntax but I cannot figure out how to return the unique set. Below is what I've tried so far:

for $a in form/Animals/Animal, $p in form/People/Person return ($a, $p)

and

form/Animals/Animal | form/People/Person

Can anyone point me in the right direction?

Thanks!

1

There are 1 answers

3
joemfb On BEST ANSWER

Update

Your for expression is correct, you just need to add an element constructor to get the result you need:

for $animal in $x/Animals/Animal,
    $person in $x/People/Person
return element union { $animal, $person }

The element name (union in the example) can be anything that you want.


These original code samples will generate the result strings from the question.

It's a little hard to read, but this will do what you want:

concat("(",
  concat(string-join(
    for $animal in /form/Animals/Animal/Name,
        $person in /form/People/Person/Name
    return concat("[", concat(string-join(($animal, $person), ", "), "]")), ", "), ")"))

If you can use an XPath/XQuery 3.0 processor, you can use the || concatenation operator instead of the concat function calls, which is (somewhat) more readable:

"(" ||
  string-join(
    for $animal in /form/Animals/Animal/Name,
        $person in /form/People/Person/Name
    return "[" || string-join(($animal, $person), ", ") || "]"
    , ", ")
|| ")"