Finding unique nodes with xslt inside a for-each - xslt

Source XML:
<r:root xmlns:r="http://root/">
<p:parent xmlns:p="http://parent/">
<p:name>John</name>
<p:age>30</age>
<c:child xmlns:c="http://child/">
<c:cname>John_child_1</cname>
<c:cage/>
<c:ItemNumber>1</ItemNumber>
</child>
<c:child xmlns:c="http://child/">
<c:cname>John_child_2</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
<c:child xmlns:c="http://child/">
<c:cname>John_child_3</cname>
<c:cage/>
<c:ItemNumber>1</ItemNumber>
</child>
</parent>
<p:parent>
<p:name>Doe</name>
<p:age>40</age>
<c:child xmlns:c="http://child/">
<c:cname>Doe_child_1</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
<c:child xmlns:c="http://child/">
<c:cname>Doe_child_2</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
</parent>
...
...
...
Target XML:
<root>
<f:father xmlns:f="http://father/">
<f:name>John</name>
<f:age>30</age>
<f:UniqueItemNumber>1</UniqueItemNumber>
<c:child xmlns:c="http://child/">
<c:cname>John_child_1</cname>
<c:cage/>
<c:ItemNumber>1</ItemNumber>
</child>
<c:child xmlns:c="http://child/">
<c:cname>John_child_3</cname>
<c:cage/>
<c:ItemNumber>1</ItemNumber>
</child>
</father>
<f:father xmlns:f="http://father/">
<f:name>John</name>
<f:age>30</age>
<f:UniqueItemNumber>2</UniqueItemNumber>
<c:child xmlns:c="http://child/">
<c:cname>John_child_2</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
</father>
<f:father xmlns:f="http://father/">
<f:name>Doe</name>
<f:age>40</age>
<f:UniqueItemNumber>2</UniqueItemNumber>
<c:child xmlns:c="http://child/">
<c:cname>Doe_child_1</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
<c:child xmlns:c="http://child/">
<c:cname>Doe_child_2</cname>
<c:cage/>
<c:ItemNumber>2</ItemNumber>
</child>
</father>
....
...
I have a source xml, which I want to convert to the shown Target xml using XSLT.
In source, we can have more than 1 parent elements, each containing multiple child. To generate the target, first we should find the distinct list of ItemNumber of all childs for each parent. Hence, the Father element in the target xml should be mapped for each unique ItemNumber in the source xml. You can say that it's like group-by clause of sql, where we are grouping on ItemNumber for each Parent. I hope that the example explains the situation.
I have been trying all sorts of thing but haven't reached even near to the solution. I am running into multiple issues while forming a solution:
1. I don't think that I can apply "Muenchian Method" since, I need to find unique ItemNumber for each Parent. Hence, the key has to be defined inside the for-each(parent) element. I am confused here.
2. I think, I should be having a top level for-each(Parent). Inside it, a way to determine unique ItemNumber. And then, when I try to use to get Parent Name, I get nothing because the xpath (/name) isn;t valid when the control is inside the second for-each(uniqueItemNumber). It's tough to explain the problem.
I am hoping that I can get a solution here. Thanks in advance.

You can use Muenchian grouping to group within an element like this, the trick is to include something unique to each parent element as part of the grouping key. Its generate-id() is usually a good candidate.
<xsl:key name="childrenByNumber "match="c:child"
use="concat(generate-id(..), '+', c:ItemNumber)"/>
When you want to extract the groups within a parent you construct the lookup key in the same way:
<xsl:template match="p:parent">
<xsl:variable name="p" select="."/>
<xsl:for-each select="c:child[generate-id() =
generate-id(key('childrenByNumber', concat(generate-id($p), '+', c:ItemNumber))[1])]">
<f:father xmlns:f="http://father/">
<f:name><xsl:value-of select="$p/p:name"/></f:name>
<f:age><xsl:value-of select="$p/p:age"/></f:age>
<f:uniqueItemNumber>
<xsl:value-of select="c:ItemNumber"/>
</f:uniqueItemNumber>
<xsl:copy-of select="key('childrenByNumber', concat(generate-id($p), '+', c:ItemNumber))"/>
</f:father>
</xsl:for-each>
</xsl:template>

Related

Python 2 XML Etree xpath : Getting Predicate error while try to parse variable in attribute check, like [#attrib ='VAL'] but using [#attrib = '%s']

Sample XML :
<ParentTag TEST_ID="xxxxxx" ID="1">
<Child TagType="Manual">
<Manhrs Cost="100"/>
<Testing VAL="RANDOM STRING"/>
</Child>
<Child TagType="Automated">
<Manhrs Cost="10"/>
<Testing VAL="RANDOM STRING2"/>
</Child>
</ParentTag>
<ParentTag TEST_ID="YYYYYY" ID="1">
<Child TagType="Manual">
<Manhrs Cost="100"/>
<Testing VAL="RANDOM STRING"/>
</Child>
<Child TagType="Automated">
<Manhrs Cost="10"/>
<Testing VAL="RANDOM STRING2"/>
</Child>
</ParentTag>
<ParentTag TEST_ID="ZZZZZZZ" ID="1">
<Child TagType="Manual">
<Manhrs Cost="100"/>
<Testing VAL="RANDOM STRING"/>
</Child>
<Child TagType="Automated">
<Manhrs Cost="10"/>
<Testing VAL="RANDOM STRING2"/>
</Child>
</ParentTag>
so if i run:
TEST_ID = xxxxxx
for n in root.findall("ParentTag[#TEST_ID = '%s']//Child[#TagType = 'Automated']", % TEST_ID):
print("FOUND")
ERROR: python2.7/xml/etree/ElementPath.py", line 224, in prepare_predicate
raise SyntaxError("invalid predicate")
SyntaxError: invalid predicate
Please note that the above same code does work in python 3, but I need this to work in Python 2.7
If there is any alternative other than looping multiple times in python 2 kindly provide the same
Also I have to use xml.etree.ElementTree
Its due to space between the attribute and value instead of with space, one should define it like this :[#TEST_ID='{}']".format(value)

XSLT: mix nodes if they have same id

i have xml with similar structure:
<Info>
<parents>
<parent1>
<id>1</id>
</parent1>
<parent2>
<id>2</id>
</parent2>
</parents>
<children>
<child>
<id>1</id>
<parentID>
1
</parentID>
<someInfoFromOneNode>
qqq
</someInfoFromOneNode>
</child>
<child>
<id>1</id>
<parentID>
1
</parentID>
<someInfoFromAnOtherNode>
qqq
</someInfoFromAnOtherNode>
</child>
<child>
<id>2</id>
<parentID>
2
</parentID>
<someInfoFromOneNode>
qqq
</someInfoFromOneNode>
</child>
<child>
<id>2</id>
<parentID>
2
</parentID>
<someInfoFromAnOtherNode>
qqq
</someInfoFromAnOtherNode>
</child>
</children>
</Info>
i need to get xml with that structure:
<Info>
<parent>
<id>1</id>
<children>
<chidl>
<id>1</id>
<someInfoFromOneNode>
qqq
</someInfoFromOneNode>
<someInfoFromAnOtherNode>
qqq
</someInfoFromAnOtherNode>
</chidl>
</children>
</parent>
<parent>
<id>2</id>
<children>
<chidl>
<id>2</id>
<someInfoFromOneNode>
qqq
</someInfoFromOneNode>
<someInfoFromAnOtherNode>
qqq
</someInfoFromAnOtherNode>
</chidl>
</children>
</parent>
</Info>
So i need to collect all children of one parent and in addition collect info related to one child. I've tried to filter children like this and displayed at least children ids(i keep the parentId):
<children>
<xsl:for-each select="/Info/children/child">
<xsl:if test="./price_groups_id = $parentId and not(./id = preceding-sibling::id)">
<child>
<id>
<xsl:value-of select="./id"/>
</id
</child>
</xsl:if>
</xsl:for-each>
</children>
But it only filters children by parent id and duplicate them, so current result is that:
<Info>
<parent>
<id>1</id>
<children>
<child>
<id>1</id>
</child>
<child>
<id>1</id>
</child>
</children>
</parent>
<parent>
<id>2</id>
<children>
<child>
<id>2</id>
</child>
<child>
<id>2</id>
</child>
</children>
</parent>
</Info>
What can i do to check previous node id and filter by it?

How to get the next node's child value inside a for-loop in XSLT

I am trying to access a value from the next node inside a for loop in XSLT.
The XML source is :
<?xml version="1.0" encoding="UTF-8"?>
<tree>
<parent>
<id>1</id>
<child>
<effective_date>01-09-2019</effective_date>
<hours>10</hours>
<dept>1</dept>
</child>
<child>
<effective_date>01-10-2019</effective_date>
<hours>20</hours>
<dept>1</dept>
</child>
<child>
<effective_date>01-10-2019</effective_date>
<class>A</class>
</child>
</parent>
<parent>
<id>2</id>
<child>
...
</child>
<child>
..
</child>
</parent>
</tree>
The desired output is that I want the next child node's Effective_date value in first validUntil tag in result like below :
<?xml version="1.0" encoding="UTF-8"?>
<employees>
<departments>
<department>
<code>1</code>
<weeklyHours>10</weeklyHours>
<validFrom>2019-09-09</validFrom>
<validUntil>2019-10-01</validUntil
</department>
<department>
<code>1</code>
<weeklyHours>20</weeklyHours>
<validFrom>2019-10-01</validFrom>
<validUntil/>
</department>
</departments>
</employees>
In my original xslt, I am inside a for loop, which I am entering conditionally based on whether a child element has change in hours or not. So this has to be accessed inside a for loop.
If the xsl:for-each is iterating over sibling elements, then you can get the next element using following-sibling::*
If you are iterating over an arbitrary sequence $SEQ, then you can get the next element using:
XSLT 2.0: subsequence($SEQ, position()+1, 1)
XSLT 1.0: <xsl:variable name="p" select="position()"/><xsl:.... select="$SEQ[$p+1]"/>
Don't make the mistake of using $SEQ[position()+1] - the value of position() changes within a predicate.

Counting nodes with certain attribute values in XSLT

Suppose I have some XML like this:
<section name="SampleSection">
<item name="ScoredItem1">
<attributes>
<scored data_type="boolean" value="true"/>
</attributes>
</item>
<item name="UnscoredItem1">
<attributes>
<scored data_type="boolean" value="false"/>
</attributes>
</item>
<item key="(3272fbb5:22)" name="ScoredItem2">
<attributes>
<scored data_type="boolean" value="true"/>
</attributes>
</item>
</section>
Now, I know, using XSLT, I can count the items that have a scored attribute like this:
<xsl:variable name="scoredItems" select="item/attributes/scored"/>
<xsl:value-of select="count($scoredItems)"/>
This will give me a value of 3, of course.
Suppose I only want to count those items for which scored is true. How do I do that using XSLT? (This should return a value of 2 for this example.
Do it like this:
<xsl:variable name="scoredItems"
select=
"item/attributes/scored[#value='true']"/>

XSLT - how to match any non-text node children?

I'm new to XSLT and I can't figure out how to get an xsl:if that matches when there are no child tags.
I want this to match:
<context>
howdy
</context>
And this not:
<context>
<child>
howdy
</child>
</context>
the relevant xpath expression should look like:
//context[not(./*)]
You could also specify count(child::*) = 0 .