I have this XML structure
<doc>
<Bundle>
<entry>
<Observation>
<id value="o1-3" />
<subject>
<reference value="Subject/1" />
</subject>
<valueQuantity>
<value value="400" />
<unit value="U" />
</valueQuantity>
<referenceRange>
<low>
<value value="0" />
<unit value="U" />
</low>
<high>
<value value="45" />
<unit value="U" />
</high>
</referenceRange>
</Observation>
</entry>
<entry>
<Observation>
<id value="o8-3" />
<subject>
<reference value="Subject/1" />
</subject>
<valueQuantity>
<value value="0.39" />
<unit value="L" />
</valueQuantity>
<referenceRange>
<low>
<value value="0.14" />
<unit value="L" />
</low>
<high>
<value value="0.35" />
<unit value="L" />
</high>
</referenceRange>
</Observation>
</entry>
</Bundle>
<Bundle>
<entry>
<Observation>
<id value="o3-4" />
<subject>
<reference value="Subject/2" />
</subject>
<valueQuantity>
<value value="10" />
<unit value="U" />
</valueQuantity>
<referenceRange>
<low>
<value value="3" />
<unit value="U" />
</low>
<high>
<value value="30" />
<unit value="U" />
</high>
</referenceRange>
</Observation>
</entry>
<entry>
<Observation>
<id value="o15-4" />
<subject>
<reference value="Subject/2" />
</subject>
<valueQuantity>
<value value="7.1" />
<unit value="m" />
</valueQuantity>
<referenceRange>
<low>
<value value="3.5" />
<unit value="m" />
</low>
<high>
<value value="5.0" />
<unit value="m" />
</high>
</referenceRange>
</Observation>
</entry>
</Bundle>
</doc>
I am developing below mechanism:
Interpret if the valueQuantity is deviated from the referenceRange, if yes, transform the entry
Extract the Observation node grouped by Observation/subject as separate document.
A correctly interpreted Observation and extracted document is below:
<?xml version="1.0" encoding="UTF-8"?>
<Interpretation xmlns="http://intelli.org/interpretation">
<Subject>Subject/1</Subject>
<Observations>
<id value="o1-3"/>
<subject>
<reference value="Subject/1"/>
</subject>
<valueQuantity>
<value value="400"/>
<unit value="U"/>
</valueQuantity>
<referenceRange>
<low>
<value value="0"/>
<unit value="U"/>
</low>
<high>
<value value="45"/>
<unit value="U"/>
</high>
</referenceRange></Observations></Interpretation>
My XSLT:
<!-- Interpretation Starts -->
<xsl:template match="valueQuantity">
<xsl:param name="value" as="xs:double*" select="value/#value" />
<xsl:param name="low" as="xs:double*" select="following::referenceRange[1]/low/value/#value" />
<xsl:param name="high" as="xs:double*" select="following::referenceRange[1]/high/value/#value" />
<xsl:if test="$value lt $low or $value gt $high">
<xsl:element name="Interpretation">
</xsl:element>
</xsl:if>
<!-- Interpretation Ends -->
<!-- Identity Transform -->
<xsl:copy-of select="." />
<!-- Extraction Starts: Locality? -->
<xsl:for-each select="parent::Observation">
<xsl:result-document include-content-type="no" href="/interpret&extract/deviation/{concat('interpretation/', id/#value, '.xml')}">
<xsl:copy-of select="." />
</xsl:result-document>
</xsl:for-each>
</xsl:template>
I guess (because you haven't explained it clearly) that you're trying to write all the entry/valueQuantity elements that have the same value for entry/subject/reference to the same output file. The spec doesn't allow that (for a number of reasons: the results would depend on order of execution, parallel execution would become very difficult, and the resulting XML document would have no outer wrapper element).
Instead, do a separate pass over the input to generate this output file, using something like
<xsl:for-each-group select="entry" group-by="subject/reference/#value">
<xsl:result-document href="{...}">
<wrapper>
<xsl:copy-of select="current-group()"/>
</wrapper>
</xsl:result-document>
</xsl:for-each-group>
Related
I'm fairly new to XSLT but am trying to get an XML file to display a certain node set through XSLT. I am using XSL 1.0. The xml looks like this:
<...>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="75" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="14.6" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="255" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="49.8" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Blood Pressure" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="100/76" unit="mm Hg" />
</observation>
</component>
</organizer>
</entry>
</...>
What I want the output to look like is something like this:
<table>
<tr>
<td>5/21/2013</td>
<td> </td>
<td>Weight: 75lbs</td>
<td>BMI: 14.6 90</td>
</tr>
<tr>
<td>5/20/2013</td>
<td>Blood Pressure: 100/76 mm Hg</td>
<td>Weight: 255lbs</td>
<td>BMI: 49.8 90</td>
</tr>
</table>
Basically, group by the effectiveTime (within the observation node), and put the blood pressure, the weight and the bmi in subsequent columns. I also need to have a blank table cell if a particular code displayname is not present for that particular date (see blood pressure not listed for the first date).
Thanks for any help. I'm picking up the XSLT, but it's taking time since there is so much.
You don't say which version of XSLT, so I've assumed 2.0:
T:\ftemp>type entries.xml
<entries>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="75" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="14.6" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="255" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="49.8" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Blood Pressure" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="100/76" unit="mm Hg" />
</observation>
</component>
</organizer>
</entry>
</entries>
T:\ftemp>call xslt2 entries.xml entries.xsl
<table>
<tr>
<td>5/21/2013</td>
<td> </td>
<td>Weight: 75 lbs</td>
<td>BMI: 14.6 98</td>
</tr>
<tr>
<td>5/20/2013</td>
<td>Blood Pressure: 100/76 mm Hg</td>
<td>Weight: 255 lbs</td>
<td>BMI: 49.8 98</td>
</tr>
</table>
T:\ftemp>type entries.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="html"/>
<xsl:template match="entries">
<!--dictate the order of the columns this way-->
<xsl:variable name="fields" select="('Blood Pressure','Weight','BMI')"/>
<!--create the table-->
<table>
<!--grouped by time-->
<xsl:for-each-group select="entry"
group-by="organizer/component/observation/effectiveTime/#value">
<tr>
<!--subset of time-->
<td>
<xsl:value-of select="substring-before(
organizer/component/observation/effectiveTime/#value,' ')"/>
</td>
<!--the fields in order-->
<xsl:for-each select="$fields">
<xsl:variable name="this"
select="current-group()[organizer/component/observation/code/#displayName=
current()]/organizer/component/observation/value"/>
<xsl:choose>
<xsl:when test="$this">
<td>
<xsl:value-of
select="concat(.,': ',$this/#value,' ',$this/#unit)"/>
</td>
</xsl:when>
<xsl:otherwise><td> </td></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</tr>
</xsl:for-each-group>
</table>
</xsl:template>
</xsl:stylesheet>
T:\ftemp>rem Done!
Here is a version of solution using XSLT 1.0 ... rather than using a variable of field names, I coded the column handling for each one.
T:\ftemp>type entries.xml
<entries>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="75" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/21/2013 12:00:00 AM" />
<value value="14.6" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Weight" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="255" unit="lbs" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="BMI" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="49.8" unit="98" />
</observation>
</component>
</organizer>
</entry>
<entry>
<organizer>
<component>
<observation>
<code displayName="Blood Pressure" />
<effectiveTime value="5/20/2013 12:00:00 AM" />
<value value="100/76" unit="mm Hg" />
</observation>
</component>
</organizer>
</entry>
</entries>
T:\ftemp>call xslt entries.xml entries1.xsl
<table>
<tr>
<td>5/21/2013</td>
<td> </td>
<td>Weight: 75 lbs</td>
<td>BMI: 14.6 98</td>
</tr>
<tr>
<td>5/20/2013</td>
<td>Blood Pressure: 100/76 mm Hg</td>
<td>Weight: 255 lbs</td>
<td>BMI: 49.8 98</td>
</tr>
</table>
T:\ftemp>type entries1.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html"/>
<xsl:key name="times" match="entry"
use="organizer/component/observation/effectiveTime/#value"/>
<xsl:template match="entries">
<!--create the table-->
<table>
<!--grouped by time-->
<xsl:for-each select="entry[generate-id(.)=
generate-id(key('times',
organizer/component/observation/effectiveTime/#value)[1])]">
<tr>
<!--subset of time-->
<td>
<xsl:value-of select="substring-before(
organizer/component/observation/effectiveTime/#value,' ')"/>
</td>
<!--the fields in order-->
<xsl:variable name="values"
select="key('times',
organizer/component/observation/effectiveTime/#value)/
organizer/component/observation"/>
<xsl:choose>
<xsl:when test="$values/code[#displayName='Blood Pressure']">
<td>
<xsl:for-each
select="$values[code/#displayName='Blood Pressure']">
<xsl:value-of
select="concat('Blood Pressure: ',value/#value,' ',value/#unit)"/>
</xsl:for-each>
</td>
</xsl:when>
<xsl:otherwise><td> </td></xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="$values/code[#displayName='Weight']">
<td>
<xsl:for-each
select="$values[code/#displayName='Weight']">
<xsl:value-of
select="concat('Weight: ',value/#value,' ',value/#unit)"/>
</xsl:for-each>
</td>
</xsl:when>
<xsl:otherwise><td> </td></xsl:otherwise>
</xsl:choose>
<xsl:choose>
<xsl:when test="$values/code[#displayName='BMI']">
<td>
<xsl:for-each
select="$values[code/#displayName='BMI']">
<xsl:value-of
select="concat('BMI: ',value/#value,' ',value/#unit)"/>
</xsl:for-each>
</td>
</xsl:when>
<xsl:otherwise><td> </td></xsl:otherwise>
</xsl:choose>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
T:\ftemp>rem Done!
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="html" indent="yes" />
<xsl:key name="kObservationByTime" match="observation" use="effectiveTime/#value" />
<xsl:template match="/">
<table>
<tr>
<th>Time</th>
<th>Blood Pressure</th>
<th>Weight</th>
<th>BMI</th>
</tr>
<xsl:apply-templates mode="group" select="
*//observation[
generate-id()
=
generate-id(key('kObservationByTime', effectiveTime/#value)[1])
][position() > last() - 3]
" />
<xsl:sort select="effectiveTime/#value" order="descending" />
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="observation" mode="group">
<xsl:variable name="time" select="effectiveTime/#value" />
<xsl:variable name="thisGroup" select="key('kObservationByTime', $time)" />
<tr>
<td><xsl:value-of select="substring-before($time, ' ')" /></td>
<td><xsl:apply-templates select="$thisGroup[code[#displayName = 'Blood Pressure']]" /></td>
<td><xsl:apply-templates select="$thisGroup[code[#displayName = 'Weight']]" /></td>
<td><xsl:apply-templates select="$thisGroup[code[#displayName = 'BMI']]" /></td>
</tr>
</xsl:template>
<xsl:template match="observation">
<xsl:value-of select="normalize-space(concat(value/#value, ' ', value/#unit))" />
</xsl:template>
</xsl:stylesheet>
gives you
<table>
<tr>
<th>Time</th>
<th>Blood Pressure</th>
<th>Weight</th>
<th>BMI</th>
</tr>
<tr>
<td>5/21/2013</td>
<td></td>
<td>75 lbs</td>
<td>14.6 98</td>
</tr>
<tr>
<td>5/20/2013</td>
<td>100/76 mm Hg</td>
<td>255 lbs</td>
<td>49.8 98</td>
</tr>
</table>
http://www.xmlplayground.com/O9Z9DT
This solution uses an <xsl:key> for grouping.
The [position() > last() - 3] predicate selects the last 2 groups for display. The alternative, [position() < 3], would select the first two. Both work in document order, while <xsl:sort> only affects output order.
Note that due to an unwise choice in date formatting, ordering your results sensibly isn't possible with pretty code.
The non-pretty code to sort your entries by date would look like this:
<xsl:sort select="
concat(
substring-after(substring-after(substring-before(effectiveTime/#value, ' '), '/'), '/'),
'-',
10 + substring-before(effectiveTime/#value, '/'),
'-',
10 + substring-before(substring-after(effectiveTime/#value, '/'), '/')
)
" order="descending" />
... or you simply use a sensible date format and the pain goes away. ;)
I have entries for an index such as the following:
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
now I'd like to get something like
<index name="a" pages="1 />
<index name="b" pages="3-4, 6" />
<index name="c" pages="7" />
Is there some ready to use function for that?
I was thinking of a two pass solution, first put the index entries in a format such as
<index name="a" pages="1 />
<index name="b" pages="3 4 6" />
<index name="c" pages="7" />
and then turn "3 4 6" into "3-4, 6". The first step is easy:
<xsl:for-each-group select="index" group-adjacent="#name">
<xsl:element name="index">
<xsl:attribute name="name" select="#name"/>
<xsl:attribute name="pages" select="current-group()/#page"/>
</xsl:element>
</xsl:for-each-group>
(the attribute 'pages' here could be something different, of course)
Now comes the difficult step, how should I iterate through the list? There must be some clever solution as to check if the next following sibling/#page (I could put the pages into a sequence) is the current #page + 1.
Here is a sample:
<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:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="entries">
<xsl:for-each-group select="entry" group-by="#name">
<index name="{current-grouping-key()}">
<xsl:attribute name="pages">
<xsl:for-each-group select="current-group()/xs:integer(#page)" group-by="position() - .">
<xsl:if test="position() gt 1">
<xsl:text>, </xsl:text>
</xsl:if>
<xsl:value-of select="if (current-group()[2])
then (current-group()[1], current-group()[last()])
else ."
separator="-"/>
</xsl:for-each-group>
</xsl:attribute>
</index>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
With the input being
<entries>
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
</entries>
I get the result
<index name="a" pages="1"/>
<index name="b" pages="3-4, 6"/>
<index name="c" pages="7"/>
Here is another solution:
<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="entries">
<xsl:for-each-group select="entry" group-by="#name">
<index name="{current-grouping-key()}">
<xsl:attribute name="pages">
<xsl:variable name="vAtribVals" as="xs:integer*">
<xsl:perform-sort select="current-group()/#page/xs:integer(.)">
<xsl:sort data-type="number"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:sequence select=
"$vAtribVals[1],
for $k in 2 to count($vAtribVals)
return
if($vAtribVals[$k] - $vAtribVals[$k -1] ne 1)
then concat(',', $vAtribVals[$k])
else
if($vAtribVals[$k+1] - $vAtribVals[$k] ne 1)
then concat('-', $vAtribVals[$k])
else()
"/>
</xsl:attribute>
</index>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<entries>
<entry name="a" page="1" />
<entry name="b" page="3" />
<entry name="b" page="4" />
<entry name="b" page="6" />
<entry name="c" page="7" />
</entries>
the wanted, correct result is produced:
<index name="a" pages="1"/>
<index name="b" pages="3-4,6"/>
<index name="c" pages="7"/>
Do note:
Thepage attributes are sorted before further processing, and this allows correct processing even if the entry elements do not occur in sorted order in the source XML document.
The processing doesn't depend on relationship between integer value and position, which makes it applicable in other cases, where the attribute values aren't integers.
How can I convert this document:
<Root>
<!-- yes, I know I don't need a 'Root' element! Legacy code... -->
<Plans>
<Plan AreaID="1" UnitID="83">
<Part ID="9122" Name="foo" />
<Part ID="9126" Name="bar" />
</Plan>
<Plan AreaID="1" UnitID="86">
<Part ID="8650" Name="baz" />
</Plan>
<Plan AreaID="2" UnitID="26">
<Part ID="215" Name="quux" />
</Plan>
<Plan AreaID="1" UnitID="95">
<Part ID="7350" Name="meh" />
</Plan>
</Plans>
</Root>
to:
<areas>
<area id="1">
<unit id="83">
<part id="9122">foo</part>
<part id="9126">bar</part>
</unit>
<unit id="86">
<part id="8650">baz</part>
</unit>
<unit id="95">
<part id="7350">meh</part>
</unit>
</area>
<area id="2">
<unit id="26">
<part id="215">quux</part>
</unit>
</area>
</areas>
Do I need to group area elements?
Here is a sample stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="k1" match="Plan" use="#AreaID"/>
<xsl:template match="Plans">
<areas>
<xsl:apply-templates select="Plan[generate-id() = generate-id(key('k1', #AreaID)[1])]" mode="group"/>
</areas>
</xsl:template>
<xsl:template match="Plan" mode="group">
<area id="{#AreaID}">
<xsl:apply-templates select="key('k1', #AreaID)"/>
</area>
</xsl:template>
<xsl:template match="Plan">
<unit id="{#UnitID}">
<xsl:apply-templates/>
</unit>
</xsl:template>
<xsl:template match="Part">
<part id="{#ID}">
<xsl:value-of select="#Name"/>
</part>
</xsl:template>
</xsl:stylesheet>
I have a source xml that contains the addresses in spot and need to transform into an xml that holds all addresses into a single element and references each one.
I am using Saxon 9.1 processor and stylesheet version 1.0.
Thank you for helping.
Source Code:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Target Code:
<?xml version="1.0" encoding="utf-8"?>
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="Prev_1" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="Curr_2" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="Mail_3" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="Prev_1" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID UniqueID="Curr_2" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID UniqueID="Mail_3" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Have played with key and generate-id as I was trying to Generate the ID's first and copy them in the address. Here is my last trial of the xslt (best result I got was to have the UniqueID's empty so I have no idea how far off this solution is :) )
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="ID_key" match="*[#ReferedID]" use="#ReferedID"/>
<xsl:template match="/">
<Application>
<ContactDetails>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<AddressDetails>
<xsl:attribute name="StartDate">
<xsl:value-of select="#StartDate"/>
</xsl:attribute>
<xsl:attribute name="Type">
<xsl:value-of select="#Type" />
</xsl:attribute>
<AddressRef>
<xsl:attribute name="ReferedID">
<xsl:value-of select="generate-id()"/>
</xsl:attribute>
</AddressRef>
</AddressDetails>
</xsl:for-each>
</ContactDetails>
<AddressSegment>
<xsl:for-each select="Application/Person/ContactDetails/AddressDetails">
<Address>
<ID>
<xsl:attribute name="UniqueID">
<xsl:value-of select="Address/ID[generate-id()=generate-id(key('ID_key',#UniqueID))]" />
</xsl:attribute>
</ID>
<City>
<xsl:attribute name="City">
<xsl:value-of select="Address/City/#City"/>
</xsl:attribute>
</City>
<Postcode>
<sl:attribute name="Postcode">
<xsl:value-of select="Address/Postcode/#Postcode"/>
</xsl:attribute>
</Postcode>
</Address>
</xsl:for-each>
</AddressSegment>
</Application>
</xsl:template>
</xsl:stylesheet>
To give you an example of how you could use generate-id and modes, the sample stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ContactDetails>
<xsl:apply-templates select="AddressDetails/Address" mode="det"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"/>
</AddressSegment>
</xsl:copy>
</xsl:template>
<xsl:template match="Address" mode="det">
<AddressDetails StartDate="{../#StartDate}" Type="{../#Type}">
<AddressRef ReferedID="{generate-id()}"/>
</AddressDetails>
</xsl:template>
<xsl:template match="Address">
<xsl:copy>
<xsl:copy-of select="#*"/>
<ID ID="{generate-id()}"/>
<xsl:copy-of select="*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
transforms the input
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
with Saxon 6.5.5 into the output
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d0e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d0e7"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d0e11"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d0e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d0e7"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d0e11"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</ContactDetails>
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<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="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" />
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails/Address"
mode="ref" />
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails/Address">
<AddressRef ReferedID="{generate-id()}" />
</xsl:template>
<xsl:template match="AddressDetails/Address" mode="ref">
<xsl:copy>
<xsl:apply-templates select="#*" />
<ID ID="{generate-id(../*)}" />
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
On this input:
<?xml version="1.0" encoding="utf-8"?>
<ContactDetails xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<AddressDetails StartDate="1992-04-03" Type="Previous">
<Address>
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<Address>
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<Address>
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressDetails>
</ContactDetails>
Produces the desired result:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3" />
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e7" />
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e11" />
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3" />
<City City="Wien" />
<Postcode Postcode="LSP-123" />
</Address>
<Address>
<ID ID="d1e7" />
<City City="Toronto" />
<Postcode Postcode="LKT-947" />
</Address>
<Address>
<ID ID="d1e11" />
<City City="Sydney" />
<Postcode Postcode="OKU-846" />
</Address>
</AddressSegment>
</Application>
Note the use of the Identity Transform, the most fundamental transformation.
Create Unique IDs and reference them in the same document
If you need to create unique ID for an element, use thegenerate-id() function. Do note that this function generates a unique identifier for a given element in the input document. Therefore if you call the function on the same element, you'll always obtain the same ID. This is really what you want.
For simplicity, in the following example transform, I've applied the templates to AddressDetails two times, each time with a different mode.
Do note the correct generation and reference of the id in the output by applying the generate-id() function on the AddressDetails node.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="ContactDetails">
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<xsl:apply-templates select="AddressDetails" mode="contact"/>
</ContactDetails>
<AddressSegment>
<xsl:apply-templates select="AddressDetails" mode="segment"/>
</AddressSegment>
</Application>
</xsl:template>
<xsl:template match="AddressDetails" mode="contact">
<xsl:copy>
<xsl:copy-of select="#*"/>
<AddressRef ReferedID="{generate-id(.)}"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AddressDetails" mode="segment">
<Address>
<ID ID="{generate-id(.)}"/>
<xsl:copy-of select="Address/*"/>
</Address>
</xsl:template>
</xsl:stylesheet>
When applied to the input provided in the question, returns:
<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ContactDetails>
<AddressDetails StartDate="1992-04-03" Type="Previous">
<AddressRef ReferedID="d1e3"/>
</AddressDetails>
<AddressDetails StartDate="1982-09-19" Type="Current">
<AddressRef ReferedID="d1e13"/>
</AddressDetails>
<AddressDetails StartDate="1977-05-27" Type="Mailing">
<AddressRef ReferedID="d1e23"/>
</AddressDetails>
</ContactDetails>
<AddressSegment>
<Address>
<ID ID="d1e3"/>
<City City="Wien"/>
<Postcode Postcode="LSP-123"/>
</Address>
<Address>
<ID ID="d1e13"/>
<City City="Toronto"/>
<Postcode Postcode="LKT-947"/>
</Address>
<Address>
<ID ID="d1e23"/>
<City City="Sydney"/>
<Postcode Postcode="OKU-846"/>
</Address>
</AddressSegment>
</Application>
I have some XML that looks like this:
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
I have some XSL that I'm using to generate Java code from this XML. Previously I've been making a key, then generating a Java class for each group.
<xsl:key name="groupsByName" match="//group" use="#name"/>
....
<xsl:for-each select="//group[generate-id(.) = generate-id(key('groupsByName',#name)[1])]">
<xsl:call-template name="class-for-group"/>
</xsl:for-each>
All was well. Now, I've discovered that some messages have groups using the same name as groups present elsewhere, but missing one of the fields. To continue the example XML from above:
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
A group named "foo" is present, but it's missing the field with name "action".
What I'd like to do is to generate a Java class for each unique subtree. Is this possible? I can't work out what the xsl:key for that would look like. The closest idea I've had is
<xsl:key name="groupsKey" match="//group" use="concat(#name,count(*))"/>
which works for the case in the example above, but is hardly elegant. If there were instead two groups named "foo" with the same number (but different types) of fields, it would fail, so it's not actually a solution.
To be clear, the ideal key (or whatever alternative) would end up calling the template only once for the "peter" and "wendy" cases above, once for the "nana" case and again once for this case:
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
...because the fields within the group are different to those in the other cases. My key above doesn't cover this case. Is there a way to do so?
This transformation fulfills the requirements:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kGroupByType" match="group"
use="#type"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates />
</xsl:variable>
<xsl:apply-templates mode="pass2"
select="ext:node-set($vrtfPass1)/*"/>
</xsl:template>
<xsl:template match="group">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:call-template name="makeType"/>
</xsl:copy>
</xsl:template>
<xsl:template mode="pass2"
match="group[generate-id()
=
generate-id(key('kGroupByType',#type)[1])
]
">
class <xsl:value-of select="concat(#name, '|', #type)"/>
</xsl:template>
<xsl:template name="makeType">
<xsl:attribute name="type">
<xsl:text>(</xsl:text>
<xsl:for-each select="*">
<xsl:value-of select="#type"/>
<xsl:if test="not(position()=last())">+</xsl:if>
</xsl:for-each>
<xsl:text>)</xsl:text>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document (with all additions):
<root>
<message name="peter">
<field type="integer" name="pa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="wendy">
<field type="string" name="wa" />
<group name="foo">
<field type="integer" name="action" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="nana">
<field type="string" name="na" />
<group name="foo">
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
<message name="hook">
<field type="string" name="ha" />
<group name="foo">
<field type="string" name="favourite_breakfast" />
<field type="integer" name="id" />
<field type="integer" name="value" />
</group>
</message>
</root>
the wanted result is produced:
class foo|(integer+integer+integer)
class foo|(integer+integer)
class foo|(string+integer+integer)
It is left as an exercise to the reader to further adjust this to produce valid names in one's PL, and also to make this work with structures of unlimited nestedness (which I may do in another answer -- however, we need a more precise definition for this more general provlem).