xslt1.0 (firefox): counting nodes with different value, muenchian-grouping? - foreach

Each new problem that I think I will be able to solve, it turns out that I can't.
So, with the following XML I would like to know how many different nodes are there that have a different #num attribute. Perhaps easier to understand looking at the wished output.
Initial XML code
<data>
<prot seq="AAA">
<node num="2">1345</node>
<node num="2">11245</node>
<node num="2">112w45</node>
<node num="7">88885</node>
</prot>
<prot seq="BBB">
<node num="2">678</node>
<node num="2">456</node>
<node num="7">4w56</node>
<node num="7">6666</node>
</prot>
<prot seq="CCC">
<node num="2">111</node>
<node num="2">222</node>
<node num="2">22w2</node>
<node num="7">333</node>
<node num="10">3433</node>
</prot>
</data>
And the wished output, so that it expresses how many different "num"s are there
<root>
<num>2</num>
<num>7</num>
<num>10</num>
</root>
I guess it can be done (as it seems to be always the case) with muenchian grouping. I just can't see it.
Thanks!

I'm new to Meunchian, too. Here's my solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="dupes" match="node" use="#num"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//node[generate-id() =
generate-id(key('dupes', #num)[1])]"/>
</root>
</xsl:template>
<xsl:template match="node">
<num val="{.}">
<xsl:value-of select="#num"/>
</num>
</xsl:template>
</xsl:stylesheet>
When I I run this with xsltproc:
~ zacharyyoung$ xsltproc so.xsl so.xml
<?xml version="1.0"?>
<root>
<num val="1345">2</num>
<num val="88885">7</num>
<num val="3433">10</num>
</root>
I added the val="{.}" bit to show which node is being used from the key grouping. If we change ...key('dupes', #num)[1]... to ...key('dupes', #num)[last()]... we can see the difference here:
<root>
<num val="22w2">2</num>
<num val="333">7</num>
<num val="3433">10</num>
</root>
For every group of nodes with corresponding values (2,7,10, etc...), the last() node in each group is selected, versus the first [1] in the previous example.
I hope this helps.

Related

Having difficulty in looping partNumber XSLT being applied shown below

Sample SOAP Input XML Given below:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Body>
<PullCustomerPartsPricingResponse xmlns="http://cdx.dealerbuilt.com/Api/0.99/">
<PullCustomerPartsPricingResult xmlns:a="http://schemas.datacontract.org/2004/07/DealerBuilt.BaseApi" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:CustomerPart>
<a:Placement>
<a:GroupId>10</a:GroupId>
</a:Placement>
<a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/DealerBuilt.Models.Parts">
<b:Description>PAD SET, RR.</b:Description>
<b:PartNumber>31500SB2100M</b:PartNumber>
<b:PartNumberFormatted>31500-SB2-100M</b:PartNumberFormatted>
</a:Attributes>
</a:CustomerPart>
<a:CustomerPart>
<a:Placement>
<a:GroupId>10</a:GroupId>
</a:Placement>
<a:Attributes xmlns:b="http://schemas.datacontract.org/2004/07/DealerBuilt.Models.Parts">
<b:Description>Kite SET, RR.</b:Description>
<b:PartNumber>60211T7J305ZZ</b:PartNumber>
</a:Attributes>
</a:CustomerPart>
</PullCustomerPartsPricingResult>
</PullCustomerPartsPricingResponse>
</s:Body>
</s:Envelope>
XSLT Code being applied Shown below:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<xsl:for-each select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']">
<partDetail>
<partNumber>
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']/*[local-name()='Attributes'] /*[local-name()='PartNumber']" />
</partNumber>
<partDescription>
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='PullCustomerPartsPricingResponse']/*[local-name()='PullCustomerPartsPricingResult']/*[local-name()='CustomerPart']/*[local-name()='Attributes'] /*[local-name()='Description']" />
</partDescription>
</partDetail>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
My Current sample Output Shown below:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
</root>
My Desired Output shown below:
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://www.dataprint.com/global/3.0/rest/">
<results>
<partDetail>
<partNumber>31500SB2100M</partNumber>
<partDescription>PAD SET, RR.</partDescription>
</partDetail>
</results>
<results>
<partDetail>
<partNumber>60211T7J305ZZ</partNumber>
<partDescription>Kite SET, RR.</partDescription>
</partDetail>
</results>
</root>
I'm trying to loop element " a:CustomerPart" to print PartNumber and b:Descriptionas Output but first a:CustomerPart data is getting repeated Twice.I Undestand there is code change is required,please help on this.
Eiríkr Útlendi your patience/suggestions is much appreciated
Inside the xsl:for-each, the selected CustomerPart item becomes the context item. You should then select children/descendants of this CustomerPart using a relative path starting at this context item, not an absolute path starting at the root of the document (/).
That is, you should replace
<xsl:value-of select="/*[local-name()='Envelope']/*[local-name()='Body']
/*[local-name()='PullCustomerPartsPricingResponse']
/*[local-name()='PullCustomerPartsPricingResult']
/*[local-name()='CustomerPart']
/*[local-name()='Attributes']
/*[local-name()='PartNumber']" />
by
<xsl:value-of select="*[local-name()='Attributes'] /*[local-name()='PartNumber']" />
or better, by
<xsl:value-of select="*:Attributes/*:PartNumber" />

Changing values that are the same from different nodes

I need to localize values within siblings that are the same. If they are the same I need to alter them.
I think I need to use following-sibling and preceding-sibling and group-by in some way. First group-by the value I am looking for so that I get the one's that are the same in the position after each other. Then using the sibling functions to find out if they are equal.
Sample:
<programs>
<event>
<start>2018-11-25T13:55:00</start>
</event>
<event>
<start>2018-11-27T17:00:00</start>
</event>
<event>
<start>2018-11-25T13:55:00</start>
</event>
<event>
<start>2018-11-25T13:55:00</start>
</event>
</programs>
Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:template match="/">
<output>
<xsl:for-each select="/programs/event">
<xsl:variable name="starttime" select="./start"/>
<startOfProgram><xsl:value-of select="$starttime"/></startOfProgram>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Desired results:
<output>
<startOfProgram>2018-11-25T13:55:00</startOfProgram>
<startOfProgram>2018-11-25T13:56:00</startOfProgram>
<startOfProgram>2018-11-25T13:57:00</startOfProgram>
<startOfProgram>2018-11-27T17:00:00</startOfProgram>
</output>
I know this is a long shot so if anyone could point me in the right direction or help me with one part of the problem I'd be very grateful.
There is lots of other elements in the sample that I have taken out that is also carried though to the output. If it matters I can include a variety of them.
Ps. Note that the value could easily be 2018-11-25T18:30:00, which would then need to be 2018-11-25T18:30:00 and the consecutive 2018-11-25T18:31:00 if there are more of the same.
The result you have shown looks as if you want to group the values as xs:dateTime values and then simply add one minute to each item in the group depending on the position:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="programs">
<output>
<xsl:for-each-group select="event/start/xs:dateTime(.)" group-by=".">
<xsl:for-each select="current-group()">
<startOfProgram>{. + (position() - 1) * xs:dayTimeDuration('PT1M')}</startOfProgram>
</xsl:for-each>
</xsl:for-each-group>
</output>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPqsHUv/1 and the above is XSLT 3 but for an XSLT 2 processor I think you only need to change the text value template I have used to an xsl:value-of:
<startOfProgram><xsl:value-of select=". + (position() - 1) * xs:dayTimeDuration('PT1M')"/></startOfProgram>
See http://xsltransform.hikmatu.com/6qVRKvJ

xmlproperty and retrieving property value of xml element if specific attribute is present/set

My Xml looks like:
<root>
<foo location="bar"/>
<foo location="in" flag="123"/>
<foo location="pak"/>
<foo location="us" flag="256"/>
<foo location="blah"/>
</root>
For foo xml element flag is optional attribute.
And when I say:
<xmlproperty file="${base.dir}/build/my.xml" keeproot="false"/>
<echo message="foo(location) : ${foo(location)}"/>
prints all locations :
foo(location) : bar,in,pak,us,blah
Is there a way to get locations only if flag is set to some value?
Is there a way to get locations only if flag is set to some value?
Not with xmlproperty, no, as that will always conflate values that have the same tag name. But xmltask can do what you need as it supports the full power of XPath:
<taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask">
<classpath path="xmltask.jar" />
</taskdef>
<xmltask source="${base.dir}/build/my.xml">
<copy path="/root/foo[#flag='123' or #flag='256']/#location"
property="foo.location"
append="true" propertySeparator="," />
</xmltask>
<echo>${foo.location}</echo><!-- prints in,us -->
If you absolutely cannot use third-party tasks then I'd probably approach the problem by using a simple XSLT to extract just the bits of the XML that you do want into another file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="targetFlag" />
<xsl:template name="ident" match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="foo">
<xsl:if test="#flag = $targetFlag">
<xsl:call-template name="ident" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Call this with the xslt task
<xslt in="${base.dir}/build/my.xml" out="filtered.xml" style="extract.xsl">
<param name="targetFlag" expression="123" />
</xslt>
This will create filtered.xml containing just
<root>
<foo location="in" flag="123"/>
</root>
(modulo changes in whitespace) and you can load this using xmlproperty in the normal way.

XSLT 2.0: Overriding nodes with grandchild nodes

I'm trying to find a way to replace a node with one that has the same name deeper down in the tree. For example, with the following input:
<root>
<foo>
<a>1</a>
<b>2</b>
<c>3</c>
<bar>
<a>100</a>
<c>5000</c>
</bar>
</foo>
</root>
I'd like to produce something like this:
<root>
<foo>
<a>100</a>
<b>2</b>
<c>5000</c>
</foo>
</root>
I need to be able to replace any number of nodes, and I'd also like to figure out the list dynamically, rather than spell out all the possibilities because there's a chance that things will change in the future. One other requirement is that order of the parent nodes must remain intact. (To be specific, my final output is going to be a CSV file so the columns need to line up with the headers.)
This is my first attempt at learning XSLT and I'm totally stumped on this one! Any help would be greatly appreciated. I'm using XSLT 2.0, BTW.
Thanks,
Mark
I apologize for the nasty SO bug which doesn't indent the formatted code!
They can't fix this for months...
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[*]
[every $leaf in .//*[not(*)]
satisfies
name($leaf) = preceding::*/name()
]
"/>
<xsl:template match=
"*[not(*) and name() = following::*/name()]">
<xsl:sequence select=
"following::*[name() = name(current())][1]"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<foo>
<a>1</a>
<b>2</b>
<c>3</c>
<bar>
<a>100</a>
<c>5000</c>
</bar>
</foo>
</root>
produces the wanted, correct result:
<root>
<foo>
<a>100</a>
<b>2</b>
<c>5000</c>
</foo>
</root>

xslt Ant Task not passing parameters to my stylesheet

I have a style sheet like this
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="testParam"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="resources/integer[#name='LOG_LEVEL']/text()">
<xsl:value-of select="$testParam"/>
</xsl:template>
</xsl:stylesheet>
And I have an input xml like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="LOG_LEVEL">3</integer>
<string name="app_name">Test Application</string>
</resources>
But when I try to call an xslt transform in ant using this:
<xslt in="in.xml" out="out.xml" style="style_above.xsl">
<outputproperty name="method" value="xml"/>
<outputproperty name="encoding" value="UTF-8"/>
<outputproperty name="indent" value="yes"/>
<param name="testParam" expression="test"/>
</xslt>
I get the following:
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<integer name="LOG_LEVEL"/>
<string name="app_name">Test Application</string>
</resources>
it doesn't seem to be changing my xslt parameter to the value I specify in my ant target
Yep, i figured out the problem was a different thing. Was about to update this was too late. I defined the xslt task in a macro and have a optional element also named param and that was the culprit. Thanks

Resources