Using muenchian grouping to select distinct values in a subtree - xslt

I know how to do muenchian grouping but that normally is applied to a whole document (?).
Here I want to find distinct values in subtrees.
e.g.
<root>
<parent>
<child id="1"/>
<child id="1"/>
<child id="2"/>
</parent>
<parent>
<child id="2"/>
<child id="3"/>
<child id="3"/>
</parent>
</root>
I want to map to
<root>
<parent>
<child id="1"/>
<child id="2"/>
</parent>
<parent>
<child id="2"/>
<child id="3"/>
</parent>
</root>
If I try
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="id_to_id" match="/root/parent/child" use="#id"/>
<xsl:template match="/root">
<root>
<xsl:for-each select="parent">
<parent>
<xsl:for-each select="child[generate-id() = generate-id(key('id_to_id',#id)[1])]">
<child id="{#id}"/>
</xsl:for-each>
</parent>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
then it does the key over the whole document and I get
<root>
<parent>
<child id="1" />
<child id="2" />
</parent>
<parent>
<child id="3" />
</parent>
</root>

So actually I've just created a solution but its a bit odd, ie generate an id for the parent node, and effectively we have a composite key of id attribute + id for the node
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="id_to_id" match="/root/parent/child" use="concat(#id,'|',generate-id(..))"/>
<xsl:template match="/root">
<root>
<xsl:for-each select="parent">
<parent>
<xsl:for-each select="child[generate-id() = generate-id(key('id_to_id',concat(#id,'|',generate-id(..)))[1])]">
<child id="{#id}"/>
</xsl:for-each>
</parent>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
is this the standard way to do this sort of thing?

heres another equally bizarre solution, still not sure this is very sensible, is there some sort of XPath parenthesis or something I can use?
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:key name="id_to_id" match="child" use="#id"/>
<xsl:template match="/root">
<root>
<xsl:for-each select="parent">
<parent>
<xsl:variable name="children">
<xsl:copy-of select="child"/>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($children)/child[generate-id() = generate-id(key('id_to_id',#id)[1])]">
<child id="{#id}"/>
</xsl:for-each>
</parent>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

now without meunchian grouping (which isnt what I want, but its a nice declarative approach), not suitable for anything but small groups of siblings.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/root">
<root>
<xsl:for-each select="parent">
<parent>
<xsl:for-each select="child[not(#id = preceding-sibling::child/#id)]">
<child id="{#id}"/>
</xsl:for-each>
</parent>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>

Related

How to group parent elements based on the child elements in XSLT

I am tring to group multiple elements based on the child element that has the same value.
Each Child1 that has the same value should be inside of a node called Group. See desired output below.
Here is a sample XML
`<?xml version="1.0" encoding="utf-8"?>
<Root>
<Parent>
<Child1>0123</Child1>
<Child2>KIK</Child2>
<Child3>YAM</Child3>
</Parent>
<Parent>
<Child1>0123</Child1>
<Child2>FIS</Child2>
<Child3>BOL</Child3>
</Parent>
<Parent>
<Child1>0123</Child1>
<Child2>TOC</Child2>
<Child3>INO</Child3>
</Parent>
<Parent>
<Child1>456</Child1>
<Child2>CHI</Child2>
<Child3>KEN</Child3>
</Parent>
<Parent>
<Child1>456</Child1>
<Child2>ALA</Child2>
<Child3>KING</Child3>
</Parent>
</Root>`
I want the output to be like the following:
`<?xml version="1.0" encoding="utf-8"?>
<Root>
<GROUP>
<Parent>
<Child1>0123</Child1>
<Child2>KIK</Child2>
<Child3>YAM</Child3>
</Parent>
<Parent>
<Child1>0123</Child1>
<Child2>FIS</Child2>
<Child3>BOL</Child3>
</Parent>
<Parent>
<Child1>0123</Child1>
<Child2>TOC</Child2>
<Child3>INO</Child3>
</Parent>
</GROUP>
<GROUP>
<Parent>
<Child1>456</Child1>
<Child2>CHI</Child2>
<Child3>KEN</Child3>
</Parent>
<Parent>
<Child1>456</Child1>
<Child2>ALA</Child2>
<Child3>KING</Child3>
</Parent>
</GROUP>
</Root>`
I was only able to copy the whole code and not able to group it based on the values on Child1.
Sharing my solution below:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="Root">
<Root>
<xsl:for-each-group select="Parent" group-by="Child1">
<xsl:choose>
<xsl:when test="count(current-group()) gt 1">
<Group>
<xsl:apply-templates select="current-group()" />
</Group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</Root>
</xsl:template>
</xsl:stylesheet>

I need to change an xml attribute value conditionaly from an external xml using Xsl

I have the following 2 xml files,
Mapping.xml
<xml>
<map ordinal="0" reverse="xxx" forward="ThisIsXxx" />
<map ordinal="0" reverse="yyy" forward="ThisIsYyy" />
<map ordinal="0" reverse="zzz" forward="thisIsZzz" />
<map ordinal="0" reverse="xx1" forward="ThisIsXx1Info" />
<map ordinal="0" reverse="yy1" forward="ThisIsYy1Info" />
<map ordinal="0" reverse="zz1" forward="ThisIsZz1Info" />
</xml>
and an input xml file with a series of elements with some of them having a 'name' attribute
second.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="xxx">
<info name="xx1">
</info>
</child>
<child name="yyy">
<info name="yy1">
</info>
</child>
<child name="zzz">
<info name="zz1">
</info>
</child>
</Children> <!-- Added by edit -->
</xml>
What I need is if direction is 'forward' then the second.xml files #name to be set to the value from #forward from the Mapping.xml such as the following,
output.xml
<?xml version="1.0" encoding="UTF-8"?>
<xml>
<Children>
<child name="ThisIsXxx">
<info name="ThisIsXx1Info">
</info>
</child>
<child name="ThisIsZzz">
<info name="ThisIsYy1Info">
</info>
</child>
<child name="ThisIsYyy">
<info name="ThisIsZz1Info">
</info>
</child>
</xml>
Xslt file I have is setting all #name="" but not getting any values from the external file..
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!-- get the mapping db -->
<xsl:variable name="mapDb" select="document('Mapping.xml')" />
<xsl:variable name="mapFwd" select="forward" />
<!-- for all #name find the #mapdb/*/#reverse == #name and set #name=#mapDb/*/forward -->
<xsl:template match="*/#name">
<xsl:choose>
<xsl:when test="$mapFwd='forward'">
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#reverse='{#name}']/#forward"/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="name">
<xsl:value-of select="$mapDb/xml/map[#forward='{#name}']/#reverse"/>
</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--Copy Everything unchanged-->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I would appreciate some help.
I think you want to use current() e.g. $mapDb/xml/map[#reverse=current()] in your comparisons in the template matching the #name attribute and of course the variable should be initialized with <xsl:variable name="mapFwd" select="'forward'"/>.

Build nodes from xpath

I have a source xml and from this source xml I like to select the nodes given by a path e.g. /shiporder/item/title and /shiporder/shipto/name from the sample source:
<?xml version="1.0" encoding="utf-16"?>
<shiporder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" orderid="orderid1">
<orderperson>orderperson1</orderperson>
<shipto>
<name>name1</name>
</shipto>
<item>
<title>foo</title>
</item>
<item>
<title>bar</title>
</item>
</shiporder>
And I like to transform those nodes to certain target tree e.g. each /shiporder/item/title from the source xml should copied to root/Customer/Name/Title in the target xml tree. So my idea was to generate for each level in the source path a template and call this template from the preceding level:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://www.functx.com">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="/shiporder"/>
</root>
</xsl:template>
<xsl:template match="/shiporder">
<Customer>
<xsl:apply-templates select="item"/>
<xsl:apply-templates select="shipto"/>
</Customer>
</xsl:template>
<xsl:template match="/shiporder/item">
<Name>
<xsl:apply-templates select="title"/>
</Name>
</xsl:template>
<xsl:template match="/shiporder/shipto">
<Address>
<xsl:apply-templates select="name"/>
</Address>
</xsl:template>
<xsl:template match="/shiporder/item/title">
<Title>
<xsl:value-of select="text()" />
</Title>
</xsl:template>
<xsl:template match="/shiporder/shipto/name">
<Street>
<xsl:value-of select="text()" />
</Street>
</xsl:template>
</xsl:stylesheet>
There for I get a huge stylesheet if I have a huge list of source-paths. Has some one a more feasible idea to reach the target?

Split first child node from the rest using XSLT

Can anyone help me to split the child nodes using XSLT. Only child1 should be split from rest of the children.
<parent>
<child1>
<child2>
<child3>
<parent>
output:
<parent>
<child1>
<element>
<child2>
<child3>
</element>
<parent>
Here's a simple solution.
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<parent>
<xsl:copy-of select="child1"/>
<element>
<xsl:copy-of select="*[not(self::child1)]"/>
</element>
</parent>
</xsl:template>
</xsl:stylesheet>
...is applied against the provided XML:
<parent>
<child1/>
<child2/>
<child3/>
</parent>
...the wanted result is produced:
<parent>
<child1/>
<element>
<child2/>
<child3/>
</element>
</parent>
If I understand correctly, you have xml like this:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="data.xsl"?>
<data>
<parent>
<child>123</child>
<child>345</child>
<child>678</child>
</parent>
</data>
And want to show it like this:
<span>123</span>
<ul>
<li>345</li>
<li>678</li>
</ul>
If it is right, use next code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<html>
<head>
<title></title>
</head>
<body>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="parent">
<strong><xsl:value-of select="child[1]/text()"/></strong>
<ul>
<xsl:for-each select="child">
<xsl:if test="position() != 1">
<li><xsl:value-of select="text()"/></li>
</xsl:if>
</xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>

xslt move attributes in child as element to parent node

I want to move my child nodes attributes as element to parent.
for ex.
Change the below xml
<Parent>
<Children>
<Child key="Name">ABC</Child>
<Child key="Age">8</Child>
<Child key="Height">140</Child>
<Child key="Class">6</Child>
</Children>
</Parent>
to
<Parent>
<Name>ABC</Name>
<Age>8</Age>
<Height>140</Height>
<Class>6</Class>
</Parent>
Hope my question is clear..
<xsl:template match="Parent">
<xsl:copy>
<xsl:apply-templates select="Children/Child"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Children/Child[#key]">
<xsl:element name="{#key}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:output indent="yes"/>
should suffice.
This compete and short transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<Parent>
<xsl:apply-templates/>
</Parent>
</xsl:template>
<xsl:template match="Child">
<xsl:element name="{#key}"><xsl:apply-templates/></xsl:element>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Parent>
<Children>
<Child key="Name">ABC</Child>
<Child key="Age">8</Child>
<Child key="Height">140</Child>
<Child key="Class">6</Child>
</Children>
</Parent>
produces the wanted, correct result:
<Parent>
<Name>ABC</Name>
<Age>8</Age>
<Height>140</Height>
<Class>6</Class>
</Parent>