separate XML results into 2+ groups using XSL - xslt

I'm having trouble creating the XSLT to do something that I think should be straightforward...
Given the following XML:
<DATA_DS>
<LIST_ITEMS>
<ITEMS>
<abc>2011</abc>
<def>ABC Company</def>
<value>23</value>
</ITEMS>
<ITEMS>
<abc>2011</abc>
<def>ABC Company 1</def>
<value>11</value>
</ITEMS>
<ITEMS>
<abc>2010</abc>
<def>ABC Company 2</def>
<value>15</value>
</ITEMS>
<ITEMS>
<abc>2010</abc>
<def>ABC Company 3</def>
<value>6</value>
</ITEMS>
<ITEMS>
<abc>2010</abc>
<def>ABC Company 4</def>
<value>44</value>
</ITEMS>
</LIST_ITEMS></DATA_DS>
how would I go about transforming it using XSLT into something like this:
<table>
<row>23</row>
<row>11</row>
</table>
<table>
<row>15</row>
<row>6</row>
<row>44</row>
</table>
the idea is to create a new "table" every time a new "abc" is encountered

In xslt 2.0 instruction for-each-group is what you are looking for.
Following stylesheet
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="/">
<output>
<xsl:for-each-group select="DATA_DS/LIST_ITEMS/ITEMS" group-by="abc">
<table>
<xsl:for-each select="current-group()">
<row>
<xsl:value-of select="current()/value" />
</row>
</xsl:for-each>
</table>
</xsl:for-each-group>
</output>
</xsl:template>
</xsl:stylesheet>
produces output
<?xml version="1.0" encoding="UTF-8"?>
<output>
<table>
<row>23</row>
<row>11</row>
</table>
<table>
<row>15</row>
<row>6</row>
<row>44</row>
</table>
</output>

Related

Sum for distinct employee using xslt

I have a xml as below:
<root>
<row>
<Leave_days>6</Leave_days>
<Maximum>10</Maximum>
<Employee>John</Employee>
</row>
<row>
<Leave_days>4</Leave_days>
<Maximum>15</Maximum>
<Employee>Albert</Employee>
</row>
<row>
<Leave_days>2</Leave_days>
<Maximum>10</Maximum>
<Employee>John</Employee>
</row>
</root>
I need to sum the 'Maximum' but not consider the repeating employee. So for the example above, the output should be 25 (10 for John and 15 for Albert - should ignore the 2nd row for John as it is repeating)
<Root>
<row>25</row>
</Root>
In the below xslt, I'm not sure how to add a condition to eliminate the repeating employee row:
<xsl:template match="root">
<Root>
<row>
<xsl:value-of select="sum(row/Maximum)"/>
</row>
</Root>
</xsl:template>
Also tried with group-by but not able to achieve the result. Please help.
I believe the simplest solution - even in XSLT 2.0 - would be to use the Muenchian grouping expression to select the distinct employee rows:
<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:key name="row-by-emp" match="row" use="Employee" />
<xsl:template match="/root">
<Root>
<row>
<xsl:value-of select="sum(row[count(. | key('row-by-emp', Employee)[1]) = 1]/Maximum)"/>
</row>
</Root>
</xsl:template>
</xsl:stylesheet>
To do this with XSLT 2.0 group-by you'd need something like:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<xsl:variable name="maxima">
<xsl:for-each-group select="row" group-by="Employee">
<xsl:copy-of select="Maximum"/>
</xsl:for-each-group>
</xsl:variable>
<Root>
<row>
<xsl:value-of select="sum($maxima/Maximum)"/>
</row>
</Root>
</xsl:template>
</xsl:stylesheet>

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?

Moving similar nodes to new node using XSLT

I have the following input xml:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<PQGetCareGaps>
<METHOD>GET</METHOD>
<contract>HXXXX</contract>
</PQGetCareGaps>
<FinalCareGapResults>
<Gap>
<CareGap>Colorectal Cancer Screening</CareGap>
<GapHistory>
<row>
<MEMBERID>AAAAAA000016-00</MEMBERID>
</row>
</GapHistory>
</Gap>
</FinalCareGapResults>
<FinalCareGapResults>
<Gap>
<CareGap>Adult BMI Assessment</CareGap>
</Gap>
</FinalCareGapResults>
</response>
I want to modify the above xml in such a way that all the <Gap> nodes should come under a new node called <TestResults>. The resultant xml should look like below:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<PQGetCareGaps>
<METHOD>GET</METHOD>
<contract>HXXXX</contract>
</PQGetCareGaps>
<TestResults>
<Gap>
<CareGap>Colorectal Cancer Screening</CareGap>
<GapHistory>
<row>
<MEMBERID>AAAAAA000016-00</MEMBERID>
</row>
</GapHistory>
</Gap>
<Gap>
<CareGap>Adult BMI Assessment</CareGap>
</Gap>
</TestResults>
</response>
Could you please help me out?
Try this
<?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:template match="/">
<xsl:apply-templates select="response" />
</xsl:template>
<xsl:template match="response">
<response>
<xsl:copy-of select="PQGetCareGaps" />
<TestResults>
<xsl:for-each select="FinalCareGapResults/Gap">
<xsl:copy-of select="." />
</xsl:for-each>
</TestResults>
</response>
</xsl:template>
</xsl:stylesheet>

Importing OAI source into Filemaker

I have a problem with importing an OAI source into Filemaker. The mapping is ok but the result is empty.
This is the source:
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dc="http://dublincore.org/documents/dcmi-namespace/" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
<responseDate>2015-01-15T12:05:11Z</responseDate>
<request verb="ListRecords" metadataPrefix="oai_dc">
http://api.memorix-maior.nl/collectiebeheer/oai-pmh/key/SORRY_THIS_KEY_I_CANNOT_SHOW/tenant/nfm
</request>
<ListRecords>
<record>
<header>
<identifier>
e:1d59bf74-a57c-11e1-af90-bf6f69fae6b6:000a80bf-e7d6-7670-b2bd-c269b2e58878
</identifier>
etc.
And this is the xslt I made:
<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="OAI-PMH/ListRecords/record">
<ROW>
<COL>
<DATA><xsl:value-of select="header/identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
To make it clear I only pointed the first field in the OAI source.
I hope you can help me to fix this.
Best regards,
Boudewijn Ridder
The reason why your attempt doesn't work is that the source XML nodes are in a namespace. You must declare this namespace in your stylesheet, assign it a prefix and use that prefix when addressing the nodes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:oai="http://www.openarchives.org/OAI/2.0/">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<METADATA>
<FIELD NAME="identifier" TYPE="TEXT"/>
</METADATA>
<RESULTSET>
<xsl:for-each select="oai:OAI-PMH/oai:ListRecords/oai:record">
<ROW>
<COL>
<DATA><xsl:value-of select="oai:header/oai:identifier"/></DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
Note:
If your input example is representative, you might want to use :
<xsl:value-of select="normalize-space(oai:header/oai:identifier)"/>
to trim the extraneous whitespace from the result.

Doing a pivot using XSLT

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>