I am trying to group all similar records based on language. But I am not able to group in XSLT.
I am using XSL KEY function group the record in XSLT. I am trying loop and add each group records to one group.
I have the following input xml.
<root>
<element name="David" language="German"></element>
<element name="Sarah" language="German"></element>
<element name="Isaac" language="English"></element>
<element name="Abraham" language="German"></element>
<element name="Jackson" language="English"></element>
<element name="Deweher" language="English"></element>
<element name="Jonathan" language="Hindi"></element>
<element name="Mike" language="Hindi"></element>
</root>
XSLT:
<?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="1.0">
<xsl:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="/">
<root>
<xsl:for-each select="key('lang',//element/#language)">
<Group>
<xsl:attribute name="name" select=".//#language"></xsl:attribute>
<member><xsl:value-of select=".//#name"/></member>
</Group>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Expected Output :
<root>
<Group name="German">
<member>David</member>
<member>Sarah</member>
<member>Abraham</member>
</Group>
<Group name="English">
<member>Isaac</member>
<member>Jackson</member>
<member>Deweher</member>
</Group>
<Group name="Hindi">
<member>Jonathan</member>
<member>Mike</member>
</Group>
</root>
Actual Output :
<root>
<Group name="German">
<member>David</member>
</Group>
<Group name="German">
<member>Sarah</member>
</Group>
<Group name="English">
<member>Isaac</member>
</Group>
<Group name="German">
<member>Abraham</member>
</Group>
<Group name="English">
<member>Jackson</member>
</Group>
<Group name="English">
<member>Deweher</member>
</Group>
<Group name="Hindi">
<member>Jonathan</member>
</Group>
<Group name="Hindi">
<member>Mike</member>
</Group>
</root>
I am getting each records separately.
Can someone please let me know what went wrong in the XSL. Thanks :)
I made some changes in your stylesheet. This should achieve the result you expect:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each select="element[count(. | key('lang', #language)[1]) = 1]">
<Group name="{#language}">
<xsl:for-each select="key('lang', #language)">
<member><xsl:value-of select="#name"/></member>
</xsl:for-each>
</Group>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The first loop selects each unique language (a node-set of size 3), and creates a context for the inner loop. The inner loop iterates through each element and selects only the ones that have the same language.
Muenchian grouping may seem hard to grasp, but you can always apply the template shown in this tutorial and not have to think much. I simply applied that template to your example.
UPDATE: Here is a solution without using for-each loops:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes"/>
<xsl:key name="lang" match="element" use="#language"></xsl:key>
<xsl:template match="root">
<xsl:copy>
<xsl:apply-templates select="element[generate-id(.) = generate-id(key('lang', #language)[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="element">
<Group name="{#language}">
<xsl:apply-templates select="key('lang', #language)" mode="member"/>
</Group>
</xsl:template>
<xsl:template match="element" mode="member">
<member><xsl:value-of select="#name"/></member>
</xsl:template>
</xsl:stylesheet>
Related
I am pretty new in XSLT and I am stuck at once place.
I have one input xml file as below
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>1</Count>
</Root>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
...
...
...
</Roots>
Now, I want to sum the count value for similar values of Value1 and Value2 so that it looks like below:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
I have been trying a lot to transform this xml and here is my code:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" >
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Roots">
<Roots>
<Root>
<xsl:for-each-group select="Root" group-by=
"concat(Value1, '+', Value2)">
<xsl:copy-of select=
"current-group()[1]/*[starts-with(name(),'key')]"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)"/>
</Count>
</xsl:for-each-group>
</Root>
</Roots>
</xsl:template>
</xsl:stylesheet>
I know I am doing wrong and could be lot easier to the expert but I really need some help and direction.
Thanks
Your XSLT was close, but move the <Root> element definition into the xsl:for-each-group. I couldn't make sense of your xsl:copy-of..., so I just reproduced the elements one-by-one. So use this simple XSLT-2.0 stylesheet:
<?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" indent="yes" omit-xml-declaration="yes" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="/Roots">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<Value1><xsl:value-of select="current-grouping-key()" /></Value1>
<Value2><xsl:value-of select="current-group()[1]/Value2" /></Value2>
<Count><xsl:value-of select="sum(current-group()/Count)" /></Count>
</Root>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It's output is:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Right now, your grouping concat(Value1, '+', Value2) will not yield three nodes since nodes at Links2 maintains two different value2 node values. Also, your identity transform template is redundant since you rewrite the XML tree directly from root, Roots.
One Grouping (only Value1)
<?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" version="2.0" exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes" />
<xsl:template match="Roots">
<Roots>
<xsl:for-each-group select="Root" group-by="Value1">
<Root>
<xsl:copy-of select="Value1|Value2"/>
<Count>
<xsl:value-of select="sum(current-group()/Count)" />
</Count>
</Root>
</xsl:for-each-group>
</Roots>
</xsl:template>
</xsl:stylesheet>
Returns with first value2 returned:
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>20</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
Two Groupings (Value1 and Value2)
Keeping original grouping:
<xsl:for-each-group select="Root" group-by="concat(Value1, '+', Value2)">
Returns four nodes
<Roots>
<Root>
<Value1>Links1</Value1>
<Value2>notadmin</Value2>
<Count>11</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>userxyz</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links2</Value1>
<Value2>usermnp</Value2>
<Count>10</Count>
</Root>
<Root>
<Value1>Links3</Value1>
<Value2>user123</Value2>
<Count>5</Count>
</Root>
</Roots>
I have soap envelop containing xml. I want to remove that tag value. I am using saxon9 parser. my input xml is:
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org /soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body><ns1:getDocumentByKeyResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://www.taleo.com/ws/integration/toolkit/2005/07">
<Document xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07">
<Attributes><Attribute name="duration">0:00:13.629</Attribute><Attribute name="count">121</Attribute><Attribute name="entity">Requisition</Attribute>
<Attribute name="mode">XML</Attribute>
<Attribute name="version">http://www.taleo.com/ws/tee800/2009/01</Attribute>
</Attributes><Content>
<ExportXML xmlns="http://www.taleo.com/ws/integration/toolkit/2005/07">
<record>
<field name="JobAction">2</field>
<field name="JobType">false</field>
<field name="JobPositionPostingID">000065</field>
<field name="JobFunctionCode">ADMINISTRATION</field>
</record>
</ExportXML></Content></Document></ns1:getDocumentByKeyResponse>
</soapenv:Body> </soapenv:Envelope>
my xsl file is like this
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*:field">
<xsl:element name="{lower-case(#name)}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*:ExportXML">
<JobPositionPostings>
<xsl:apply-templates/>
</JobPositionPostings>
</xsl:template>
<xsl:template match="*:record">
<JobPositionPosting>
<xsl:apply-templates select="*:field[starts-with(#name,'JobAction')]"/>
<HiringOrg>
<xsl:apply-templates select="*:field[starts-with(#name,'JobType')]"/>
<Industry>
<xsl:apply-templates select="*:field[starts-with(#name,'JobPositionPostingID')]"/>
</Industry>
</HiringOrg>
</JobPositionPosting>
<xsl:apply-templates select="*:field[starts-with(#name,'JobFeedResponseEmail')]"/>
</xsl:template>
<xsl:template match="*:field[#name='TypeName']"/>
<xsl:template match="*:field[#name='TypeName']" mode="title">
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
I am getting output like this
<?xml version="1.0" encoding="utf-8"?>**0:00:13.629121RequisitionXMLhttp://www.taleo.com/ws/tee800/2009/01**<JobPositionPostings xmlns:soap="http://www.taleo.com/ws/integration/toolkit/2005/07">
<JobPositionPosting>
<jobaction>2</jobaction>
<jobtype>false</jobtype>
<jobpositionpostingid>000065</jobpositionpostingid>
<HiringOrg>
after xml tag it is picking all attribute tag value which I don't want.
If you add a empty template like this one
<xsl:template match="*:Attributes"/>
(Notice the slash at end)
All Attributes elements will be ignored.
Since you don't seem to care about anything outside the <Content> you could add an extra template to jump straight to that and ignore the rest:
<xsl:template match="/">
<xsl:apply-templates select="descendant::*:Content[1]" />
</xsl:template>
I've been trying to figure this our for a while, but no luck.
I have a for each loop specifically targeting to a node with specific attributes, but somehow the for each condition doesn't seem to work.
I have the following xml
<?xml version="1.0" encoding="UTF-8"?>
<application-template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<config>
<env value="testing">
<subEnv value="env">
<group value="group2s">
<group value="group2as">
<prop value="group2Props">group2PropValues</prop>
</group>
</group>
</subEnv>
<subEnv value="misc1">
<group value="group2s">
<group value="group2as">
<prop value="group2Props">group2sPropValues</prop>
</group>
</group>
<group value="group2s1">
<group value="group2as1">
<prop value="group2Props">group2s1PropValues</prop>
</group>
</group>
</subEnv>
</env>
<env value="testingA">
<subEnv value="env">
<group value="test2">
<group value="test2a">
<group value="test2ab">
<prop value="group2Props">testingAGroup2PropValues</prop>
</group>
</group>
</group>
</subEnv>
</env>
</config>
</application-template>
with the following xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="inputEnv"/>
<xsl:variable name="env" select="/application-template/config/env[#value=$inputEnv]"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos" select="$env/subEnv"/>
<xsl:with-param name="subEnvValue" select="$env/subEnv/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="ou">
<xsl:param name="subEnvPos"/>
<xsl:param name="subEnvValue"/>
<xsl:variable name="test" select="/application-template/config/env[#value=$inputEnv]/subEnv[#value=$subEnvValue]"/>
<xsl:for-each select="$test/group">
testing1:
<xsl:value-of select="$test"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The param inputEnv is "testing" and the param $subEnvValue is "env". The output I got is
testing1:group2PropValues
testing1:group2PropValues
testing1:group2PropValues
testing1:group2PropValues
But I want to just loop once since the the condition is to match the subenv node = $subEnvValue (which is "env"). The output I'm hoping for is
testing1:group2PropValues
I think that instead of your current code:
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv/#value"/>
</xsl:call-template>
</xsl:template>
you want something like this:
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv[1]"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv[1]/#value"/>
</xsl:call-template>
</xsl:template>
With this modification, the complete XSLT code now is:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:param name="inputEnv" select="'testing'"/>
<xsl:variable name="env" select=
"/application-template/config/env[#value=$inputEnv]"/>
<xsl:strip-space elements="*"/>
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:call-template name="ou">
<xsl:with-param name="subEnvPos"
select="$env/subEnv[1]"/>
<xsl:with-param name="subEnvValue"
select="$env/subEnv[1]/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="ou">
<xsl:param name="subEnvPos"/>
<xsl:param name="subEnvValue"/>
<xsl:variable name="test" select=
"/application-template/config/env
[#value=$inputEnv]/subEnv[#value=$subEnvValue]"/>
<xsl:for-each select="$test/group">
testing1:
<xsl:value-of select="$test"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
and when applied to the provided XML document:
<application-template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<config>
<env value="testing">
<subEnv value="env">
<group value="group2s">
<group value="group2as">
<group value="group2bs">
<prop value="group2Props">group2PropValues</prop>
</group>
</group>
</group>
</subEnv>
<subEnv value="misc1">
<group value="group2s">
<group value="group2as">
<group value="group2bs">
<prop value="group2Props">group2sPropValues</prop>
</group>
</group>
</group>
<group value="group2s1">
<group value="group2as1">
<group value="group2bs1">
<prop value="group2Props">group2s1PropValues</prop>
</group>
</group>
</group>
</subEnv>
<subEnv value="misc2">
<group value="group2sMisc2">
<group value="group2asMisc2">
<group value="group2bsMisc2">
<prop value="group2Props">group2PropValuesMisc2</prop>
</group>
</group>
</group>
</subEnv>
</env>
</config>
</application-template>
the wanted result is produced:
testing1:
group2PropValues
Update:
After the OP explained in a comment what he wants, here is a short and simple solution to the new problem:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:param name="vinputEnv" select="'testing'"/>
<xsl:template match="/*/*/env">
<xsl:if test="#value = $vinputEnv">
<xsl:for-each select="descendant::*[not(*)]">
<xsl:value-of select="concat('testing1: ', ., '
')"/>
</xsl:for-each>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied to the provided XML document (above), the wanted, correct result is produced:
testing1: group2PropValues
testing1: group2sPropValues
testing1: group2s1PropValues
testing1: group2PropValuesMisc2
I have to get the attribute names and do some manipulations based on the its name in XSLT.
Source:
<group xlink:type="simple" xlink:href="XXX" xlink:title="sectionHeader_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="YYY" xlink:title="BodyParagraph_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="ZZZ" xlink:title="sectionHeader_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="AAA" xlink:title="sectionHeader_3" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="BBB" xlink:title="BodyParagraph_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="BBB" xlink:title="ConditionalText_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
I have to get the attribute xlink:title attribute in it and check for the following:
when the attribute xlink:title contains string sectionHeader , I need to do some manipulations.
when the attribute xlink:title contains string BodyParagraph, I need to some manipulations.
when the attribute xlink:title contains string ConditionalText, I need to some manipulations.
Can any one explain how it can be done?
It is in the spirit of XSLT to use templates and pattern matching so that explicit conditional instructions are minimized or eliminated altogether.
Here is how this can be done:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="group">
<xsl:apply-templates select="#xlink:title"/>
</xsl:template>
<xsl:template match="#xlink:title[contains(., 'sectionHeader')]">
Found #xlink:title containing "sectionHeader"
</xsl:template>
<xsl:template match="#xlink:title[contains(., 'BodyParagraph')]">
Found #xlink:title containing "BodyParagraph"
</xsl:template>
<xsl:template match="#xlink:title[contains(., 'ConditionalText')]">
Found #xlink:title containing "ConditionalText"
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML fragment (converted to a well-formed XML document):
<t>
<group xlink:type="simple" xlink:href="XXX" xlink:title="sectionHeader_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="YYY" xlink:title="BodyParagraph_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="ZZZ" xlink:title="sectionHeader_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="AAA" xlink:title="sectionHeader_3" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="BBB" xlink:title="BodyParagraph_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group xlink:type="simple" xlink:href="BBB" xlink:title="ConditionalText_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
</t>
the wanted result (something done in each case) is produced:
Found #xlink:title containing "sectionHeader"
Found #xlink:title containing "BodyParagraph"
Found #xlink:title containing "sectionHeader"
Found #xlink:title containing "sectionHeader"
Found #xlink:title containing "BodyParagraph"
Found #xlink:title containing "ConditionalText"
Do note: You may consider using the starts-with() function rather than contains() .
Since you can't modify parts of an existing XML file with XSLT, you have to copy everything and change those parts that should be different. Thus, I suggest to write a template that copies each node by default. Then you can add specialized templates for the group elements that meet your conditions, e.g. something like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="group[contains(#xlink:title,'sectionHeader')]">
<group>
<xsl:copy-of select="#*"/>
<!-- modifications here -->
</group>
</xsl:template>
<xsl:template match="group[contains(#xlink:title,'BodyParagraph')]">
<group>
<xsl:copy-of select="#*"/>
<!-- modifications here -->
</group>
</xsl:template>
<xsl:template match="group[contains(#xlink:title,'ConditionalText')]">
<group>
<xsl:copy-of select="#*"/>
<!-- modifications here -->
</group>
</xsl:template>
</xsl:stylesheet>
If you want to change the attribute values too, just replace the xsl:copy-of statements with the desired modifications.
Your source XML (some modifications done):
<?xml version="1.0"?>
<root>
<group id="x1" xlink:type="simple" xlink:href="XXX" xlink:title="sectionHeader_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x2" xlink:type="simple" xlink:href="YYY" xlink:title="BodyParagraph_1" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x3" xlink:type="simple" xlink:href="ZZZ" xlink:title="sectionHeader_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x4" xlink:type="simple" xlink:href="AAA" xlink:title="sectionHeader_3" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x5" xlink:type="simple" xlink:href="BBB" xlink:title="BodyParagraph_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x6" xlink:type="simple" xlink:href="BBB" xlink:title="ConditionalText_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
<group id="x7" xlink:type="simple" xlink:href="BBB" xlink:title="some_other_2" xmlns:xlink="http://www.w3.org/1999/xlink"></group>
</root>
XSL Document:
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/1999/xlink">
<xsl:template match="/">
<elements>
<xsl:apply-templates/>
</elements>
</xsl:template>
<xsl:template match="//group[(contains(#xlink:title,'sectionHeader') or contains(#xlink:title,'BodyParagraph') or contains(#xlink:title,'ConditionalText'))]">
<xsl:element name="element">
<xsl:attribute name="id"><xsl:value-of select="#id"/></xsl:attribute>
<xsl:attribute name="type"><xsl:value-of select="#xlink:type"/></xsl:attribute>
<xsl:attribute name="href"><xsl:value-of select="#xlink:href"/></xsl:attribute>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
And the result:
<?xml version='1.0' ?>
<elements xmlns:xlink="http://www.w3.org/1999/xlink">
<element id="x1" type="simple" href="XXX"/>
<element id="x2" type="simple" href="YYY"/>
<element id="x3" type="simple" href="ZZZ"/>
<element id="x4" type="simple" href="AAA"/>
<element id="x5" type="simple" href="BBB"/>
<element id="x6" type="simple" href="BBB"/>
</elements>
I have an xml file like this:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
I want to transform this using XSLT to get something like:
<root>
<items><status>good</status>
<name>one</name>
<name>two</name>
</items>
<items><status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items><status>ugly</status>
<name>four</name>
</items>
</root>
In other words, I get a list of items, each with a status, and I want to turn it into a list of statuses, each with a list of items.
My initial thought was to do apply-templates matching each status type in turn, but that means I have to know the complete list of statuses. Is there a better way to do it?
Thanks for any help.
Muench to the rescue!
<?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" indent="yes" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each select="/root/item/status[generate-id() = generate-id(key('muench',.)[1])]">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>
Yes, this can be done in XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<!-- -->
<xsl:key name="kStatByVal"
match="status" use="."/>
<!-- -->
<xsl:key name="kItemByStat"
match="item" use="status"/>
<!-- -->
<xsl:variable name="vDoc" select="/"/>
<xsl:template match="/">
<top>
<xsl:for-each select=
"/*/*/status[generate-id()
=
generate-id(key('kStatByVal',.)[1])
]">
<items>
<status><xsl:value-of select="."/></status>
<xsl:for-each select="key('kItemByStat', .)">
<xsl:copy-of select="name"/>
</xsl:for-each>
</items>
</xsl:for-each>
</top>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the original XML document:
<root>
<item>
<name>one</name>
<status>good</status>
</item>
<item>
<name>two</name>
<status>good</status>
</item>
<item>
<name>three</name>
<status>bad</status>
</item>
<item>
<name>four</name>
<status>ugly</status>
</item>
<item>
<name>five</name>
<status>bad</status>
</item>
</root>
The wanted result is produced:
<top>
<items>
<status>good</status>
<name>one</name>
<name>two</name>
</items>
<items>
<status>bad</status>
<name>three</name>
<name>five</name>
</items>
<items>
<status>ugly</status>
<name>four</name>
</items>
</top>
Do note the use of:
The Muenchian method for grouping
The use of <xsl:key> and the key() function
It depends about your xslt engine. If you're using xslt 1.0 without any extension, then your approach is certainly the best.
On the other side, if you're allowed to use exslt (especially the node-set extension) or xslt 2.0, then you could do it in a more generic way:
Collect all the available statuses
Create a node-set from the obtained result
Iterating on this node set, create your pivot by filtering you status base on the current element in your iteration.
But before doing that, consider that it may be overkill if you only have a few set of statuses and that adding another status is quite rare.
In XSLT 2.0 you can replace the muenchian grouping by its standard grouping mechanism. Applied to the given answer the xslt would look like:
<?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" indent="yes" encoding="UTF-8"/>
<xsl:key name="muench" match="/root/item/status" use="."/>
<xsl:template match="/">
<root>
<xsl:for-each-group select="/root/item/status" group-by="key('muench', .)">
<xsl:call-template name="pivot">
<xsl:with-param name="status" select="."/>
</xsl:call-template>
</xsl:for-each-group>
</root>
</xsl:template>
<xsl:template name="pivot">
<xsl:param name="status"/>
<items>
<status><xsl:value-of select="$status"/></status>
<xsl:for-each select="/root/item[status=$status]">
<name><xsl:value-of select="name"/></name>
</xsl:for-each>
</items>
</xsl:template>
</xsl:stylesheet>