how to select number of element values randomly in XSLT - xslt

I want to select 30% of values randomly from a XML.As Here is 10 values so , i have to select 3 values randomly.
i have a xml.
<xml>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
<value>E</value>
<value>F</value>
<value>G</value>
<value>H</value>
<value>I</value>
<value>J</value>
</xml>
I need this value.
<xml>
<value>E</value>
<value>I</value>
<value>J</value>
</xml>

In a modern XSLT 3 implementation supporting the random-number-generator function you can use e.g. random-number-generator()?permute(/xml/value)[position() = 1 to 3]:
let $xml := <xml>
<value>A</value>
<value>B</value>
<value>C</value>
<value>D</value>
<value>E</value>
<value>F</value>
<value>G</value>
<value>H</value>
<value>I</value>
<value>J</value>
</xml>,
$values := $xml/value,
$length := xs:integer(count($values) * .3)
return random-number-generator()?permute($values)[position() = 1 to $length]
So you need an XSLT 3 processor like Saxon 10 in any edition or Saxon 9 PE or EE or AltovaXML 2017 R3 or later.
In XSLT 1, if you have support for EXSLT's math:random, you can use
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:math="http://exslt.org/math">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="xml">
<xsl:copy>
<xsl:apply-templates select="value[position() < 4]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="value">
<xsl:copy>
<xsl:variable name="pos" select="floor(math:random() * 10 + 1)"/>
<xsl:value-of select="../value[$pos]"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
http://xsltransform.net/93Fbify

Related

Applying template on differents node's level

I try to modify all the node's QName which contain a certain string :
and return new one without.
(e.g. I have <RandomName> and want to render <Name>)
I have a xsl:template working for the first level node, but can't find how to apply it to lower levels.
e.g. XML Source
here I would want to remove all the `Default`
<?xml version="1.0" encoding="UTF-8"?>
<Object>
<DefaultID>XXXXXXX</DefaultID>
<DefaultType>Random</DefaultType>
<DefaultCustomer>
<DefaultID>XXXXXXX</DefaultID>
<DefaultName>John Doe</DefaultName>
<DefaultAddress>33th Whitecaslt Blvd.</DefaultAddress>
<DefaultNumber>+XX X XX XX XX XX</DefaultNumber>
</DefaultCustomer>
</Object>
Expected Result
<?xml version="1.0" encoding="UTF-8"?>
<Object>
<ID>XXXXXXX</ID>
<Type>Random</Type>
<Customer>
<ID>XXXXXXX</ID>
<Name>John Doe</tName>
<Address>33th Whitecaslt Blvd.</Address>
<Number>+XX X XX XX XX XX</Number>
</Customer>
</Object>
My XSL
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:if test="string-length(.)>0">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<xsl:template match="//*[contains(name(), 'Default')]">
<xsl:element name="{substring-after(name(), 'Default')}">
<xsl:copy select=".">
<xsl:apply-templates match="//*[contains(name(), 'Default')]" />
</xsl:copy>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
What I Have
<?xml version="1.0" encoding="UTF-8"?>
<Object>
<ID>XXXXXXX</ID>
<Type>Random</Type>
<Customer>
<DefaultID>XXXXXXX</DefaultID>
<DefaultName>John Doe</DefaultName>
<DefaultAddress>33th Whitecaslte Blvd.</DefaultAddress>
<DefaultNumber>+XX X XX XX XX XX</DefaultNumber>
</Customer>
</Object>
as you can see the template doesn't match with the second node's level.
I suppose that my problem come from the scope of my XPath in my <xsl:copy select='.'> (that's appear to be useless btw) ? or maybe here, <xsl:copy> isn't the best choice (same result w/ <xsl:copy-of>)?
You are overthinking this. Try simply:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(), 'Default')]">
<xsl:element name="{substring-after(name(), 'Default')}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT to remove all but one complex element

I have a large XML (from Agile) that I need to pass into a system that cannot quickly read large XMLs. There is a repeating element that has a unique child element, I'd like to remove all of the others except the one I pass in with a parameter.
<AgileData xmlns="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<Parts uniqueId="10000:6049719:8336339">
<TitleBlock><Number>300901</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Production</LifecyclePhase>
<Description>Prod 1 Desc</Description>
<ProductLines><Value>123-Line</Value></ProductLines>
</Parts>
<Parts uniqueId="10000:6049719:8337000">
<TitleBlock><Number>300902</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 2 Desc</Description>
<ProductLines><Value>222-Line</Value></ProductLines>
</Parts>
<Parts uniqueId="10000:6049719:8337034">
<TitleBlock><Number>300908</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 3 Desc</Description>
<ProductLines><Value>123-Line</Value></ProductLines>
</Parts>
</AgileData>
Desired Output is the following when passing in the parameter Item
<AgileData xmlns="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<Parts uniqueId="10000:6049719:8337000">
<TitleBlock><Number>300902</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 2 Desc</Description>
<ProductLines><Value>222-Line</Value></ProductLines>
</Parts>
</AgileData>
This what I have so far, though it's not working.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:param name="Item" select="300902" />
<xsl:template match="/AgileData">
<xsl:apply-templates select="//Parts"/>
</xsl:template>
<xsl:template match="//Parts/*[TitleBlock/Number='$Item']">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
After its working I'll change the parameter to just have a name and not a hardcoded value as it will get passed in from the engine. I can use XSLT 1.0 and 2.0.
Or simply:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:param name="Item" select="300902" />
<xsl:template match="/ns0:AgileData">
<xsl:copy>
<xsl:copy-of select="ns0:Parts[ns0:TitleBlock/ns0:Number=$Item]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
If you can use XSLT 2.0, it can be even simpler:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xpath-default-namespace="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:param name="Item" select="300902" />
<xsl:template match="/AgileData">
<xsl:copy>
<xsl:copy-of select="Parts[TitleBlock/Number=$Item]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This XSLT 2.0 transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="pItem" select="300902" />
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="x:Parts[x:TitleBlock/x:Number/number() ne $pItem]"/>
</xsl:stylesheet>
When applied on the provided XML document:
<AgileData xmlns="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<Parts uniqueId="10000:6049719:8336339">
<TitleBlock><Number>300901</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Production</LifecyclePhase>
<Description>Prod 1 Desc</Description>
<ProductLines><Value>123-Line</Value></ProductLines>
</Parts>
<Parts uniqueId="10000:6049719:8337000">
<TitleBlock><Number>300902</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 2 Desc</Description>
<ProductLines><Value>222-Line</Value></ProductLines>
</Parts>
<Parts uniqueId="10000:6049719:8337034">
<TitleBlock><Number>300908</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 3 Desc</Description>
<ProductLines><Value>123-Line</Value></ProductLines>
</Parts>
</AgileData>
produces the wanted, correct result:
<AgileData xmlns="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<Parts uniqueId="10000:6049719:8337000">
<TitleBlock><Number>300902</Number></TitleBlock>
<PartType>Raw Material</PartType>
<LifecyclePhase>Prototype</LifecyclePhase>
<Description>Prod 2 Desc</Description>
<ProductLines><Value>222-Line</Value></ProductLines>
</Parts>
</AgileData>
XSLT 1.0 solution:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://www.oracle.com/webfolder/technetwork/xml/plm/2013/09/">
<xsl:output omit-xml-declaration="yes"/>
<xsl:param name="pItem" select="300902" />
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="x:Parts">
<xsl:if
test="x:TitleBlock/x:Number = $pItem"><xsl:call-template name="identity"/></xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct / wanted result is produced.
Do note:
The use and overriding of the identity rule
The use of the same namespace as the default namespace in the XML document.
To learn more about the either of these important concepts, do search SO or the internet for "XSLT identity template / rule" and "XSLT processing a document in default namespace"
See also:
Dave Pawson's XSLT FAQ on Identity transformation
Dave Pawson's XSLT FAQ on Namespaces

XSLT 1.0 count and then copy structure where child ends-with

I have to copy all the sets where the mail end with "test.com", if there count is greater than 5. I've tried several things, but nothing seems to work.
How can I do this with xslt 1.0?
<root>
<sets>
<set>
<mail>a#test.com</mail>
<foo/>
</set>
<set>
<mail>a#test.net</mail>
<foo/>
</set>
<set>
<mail>b#test.com</mail>
<foo/>
</set>
</sets>
</root>
For example I tried this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:if test="count(/root/sets/set[mail = '*test.com'])">
<root>
<sets>
<xsl:for-each select="/root/sets/set">
<xsl:if test="contains(./mail, 'test.com')">
<xsl:copy-of select="./*"/>
</xsl:if>
</xsl:for-each>
</sets>
</root>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I have to copy all the sets where the mail end with "test.com".
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="sets">
<xsl:copy>
<xsl:apply-templates select="set[substring-after(mail, '#')='test.com']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

XSLT: how to reverse the tree?

I need to transform a document like this:
<root>
<products>
<ProductInfo>
<ProductID>0</ProductID>
<ProductName>Hello world!</ProductName>
</ProductInfo>
<M>
<ModelInfo>
<ModelID>0</ModelID>
<ModelName>Hello world!</ModelName>
</ModelInfo>
</M>
</products>
</root>
Into this:
<root>
<products>
<M>
<ModelInfo>
<ModelName>Hello world!</ModelName>
<ModelID>0</ModelID>
</ModelInfo>
</M>
<ProductInfo>
<ProductName>Hello world!</ProductName>
<ProductID>0</ProductID>
</ProductInfo>
</products>
</root>
So all the tags in the output should be in reversed order.
I need this for testing: I need to ensure that some external application accepts the tags in any order; and also I need it to test that my XML Schema allows tags in any order.
Not reverse the tree, but reverse the order of sibling branches (at all levels):
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()">
<xsl:sort select="position()" data-type="number" order="descending"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

xslt extract and sort leaf nodes by name - unexpected result

I wanted to extract leaf nodes and have them sorted.
My XSL gives unexpected results. How can I solve this?
Input
<root>
<b>
<b33 zzz="2" fff="3"></b33>
<b11></b11>
<b22></b22>
</b>
<a>
<a27></a27>
<a65 fff="0" eee="2" zzz="10"></a65>
<a11></a11>
</a>
</root>
Xsl
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:call-template name="leafnodes"/>
</root>
</xsl:template>
<xsl:template match="*[not(*)]|#*" name="leafnodes">
<xsl:copy>
<xsl:apply-templates select="node()">
<xsl:sort select="name()"/>
</xsl:apply-templates>
<xsl:apply-templates select="#*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output (I would expected it to be sorted, it is not)
<root>
<b33 fff="3" zzz="2" />
<b11 />
<b22 />
<a27 />
<a65 eee="2" fff="0" zzz="10" />
<a11 />
</root>
I would expect the nodes in the order a11, a27, a65, b11, b22, b33.
If I leave out the '[not(*)]', the xsl takes all nodes and sorts them properly.
How can this be solved?
To output all element which have no child sorted by name and the attributes also sorted by name. Try this;
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="//*[not(*)]">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*" >
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Which will generate following output:
<root>
<a11/>
<a27/>
<a65 eee="2" fff="0" zzz="10"/>
<b11/>
<b22/>
<b33 fff="3" zzz="2"/>
</root>