I have this source XML tree:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<foo>
<bar>
<baz>
<item>
<methods>
<item>
<id>1</id>
</item>
</methods>
<id>1</id>
</item>
<item>
<methods>
<item>
<id>19</id>
</item>
</methods>
<id>2</id>
</item>
</baz>
</bar>
</foo>
<bar_method>
<root>
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
<method id="2">
<calc md="qck" />
<tm m="4" />
<price_list>
<price mse="1">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
</root>
</bar_method>
</root>
Now I need to place fragment of this tree in variable using XPath. The fragment should look like this:
<bla id="1">
<methods>
<method id="1">
<calc md="ck" />
<tm m="14" />
<price_list>
<price mse="0">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd" />
<tm m="3" />
<price_list>
<price mse="01">
<ins re="0" />
</price>
</price_list>
</method>
</methods>
</bla>
These are bla nodes excluding method nodes, id attributes of which missing in /root/foo/bar/baz/item/methods/item/id. I use following expression but it selects all nodes with duplicates:
<xsl:variable name="meth" select="/root/bar_method/root//*[not(name() = 'method' and count(/root/foo/bar/baz//methods/item[id = #id]) = 0)]" />
XPath can only select nodes, it cannot change them. That is to say, the children and descendants of the nodes you select will always be exactly as they were in the source document.
If you want to create a tree that's different from the input tree, you need XSLT or XQuery.
Looks like you want all the bla elements and just the first methods/method element within each of them. Is that right?
You can't do that in a single XPath expression because you can only restrict the elements being selected - you can't filter out some of their descendants as well. But it is possible using templates.
This stylesheet creates the variable $meth and outputs it using copy-of.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:variable name="meth">
<xsl:apply-templates select="root/bar_method/root/bla"/>
</xsl:variable>
<xsl:copy-of select="$meth"/>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="methods">
<xsl:copy>
<xsl:apply-templates select="method[1]"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
output
<bla id="1">
<methods>
<method id="1">
<calc md="ck"/>
<tm m="14"/>
<price_list>
<price mse="0">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>
<bla id="2">
<methods>
<method id="19">
<calc md="dd"/>
<tm m="3"/>
<price_list>
<price mse="01">
<ins re="0"/>
</price>
</price_list>
</method>
</methods>
</bla>
Related
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>
Iam novice to XSLT mapping and looking for help to achieve desired output. I want to copy Input XML as is in to output.
I have tried using xsl:copy-of select which is adding an empty xmls=”” attribute in to /Inventory/Action which is not acceptable by target.
Also When I try xsl:for-each using below XSLT values for elements like AdAction , unit<4567 7896> and ID<12345 67890> QU<200 100> are repeating for each iteration .
Input XML.
<?xml version="1.0" encoding="UTF-8"?>
<Body>
<Inventory>
<Action MessageType="hello" Type="hi" Action="hand" InventoryState="avail">
<DateTime/>
<Ad Action="Set">
<Position>
<Unit TypeCode="abc">4567</Unit>
<Item>
<ID>12345</ID>
</Item>
</Position>
<Qu>200</Qu>
</Ad>
</Action>
<Action MessageType="hello" Type="hi" Action="hand" InventoryState="avail">
<DateTime/>
<Ad Action="Set">
<Position>
<Unit TypeCode="abc">7896</Unit>
<Item>
<ID>67890</ID>
</Item>
</Position>
<Qu>100</Qu>
</Ad>
</Action>
</Inventory>
XSLT mapping I have used.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/XSL/Format" exclude-result-prefixes="xsl">
<xsl:output method="xml" version="1.0" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<Body>
<xsl:for-each select="//Action">
<Action>
<xsl:attribute name="Type">hello</xsl:attribute>
<xsl:attribute name="AType">hi</xsl:attribute>
<xsl:attribute name="Action">hand</xsl:attribute>
<xsl:attribute name="State">avail</xsl:attribute>
<DateTime>DateTime</DateTime>
<Ad>
<xsl:attribute name="Action"><xsl:value-of select="//Ad/#Action"/></xsl:attribute>
<Position>
<Unit>
<xsl:attribute name="TypeCode">Store</xsl:attribute>
</Unit>
<Item>
<ID>
<xsl:value-of select="//Action/Ad/Position/Item/ID"/>
</ID>
</Item>
</Position>
<Qu>
<xsl:value-of select="//Action/Ad/Qu"/>
</Qu>
</Ad>
</Action>
</xsl:for-each>
</Body>
</xsl:template>
Output:
<Body xmlns="http://www.w3.org/1999/XSL/Format">
<Action Type="hello" AType="hi" Action="hand" State="avail">
<DateTime>DateTime</DateTime>
<Ad Action="Set Set">
<Position>
<Unit TypeCode="Store"/>
<Item>
<ID>12345 67890</ID>
</Item>
</Position>
<Qu>200 100</Qu>
</Ad>
</Action>
<Action Type="hello" AType="hi" Action="hand" State="avail">
<DateTime>DateTime</DateTime>
<Ad Action="Set Set">
<Position>
<Unit TypeCode="Store"/>
<Item>
<ID>12345 67890</ID>
</Item>
</Position>
<Qu>200 100</Qu>
</Ad>
</Action>
Desired Output no repeating Values.
<Body xmlns="http://www.w3.org/1999/XSL/Format">
<Action Type="hello" AType="hi" Action="hand" State="avail">
<DateTime>DateTime</DateTime>
<Ad Action="Set">
<Position>
<Unit TypeCode="Store"/>
<Item>
<ID>12345</ID>
</Item>
</Position>
<Qu>200</Qu>
</Ad>
</Action>
<Action Type="hello" AType="hi" Action="hand" State="avail">
<DateTime>DateTime</DateTime>
<Ad Action="Set">
<Position>
<Unit TypeCode="Store"/>
<Item>
<ID>67890</ID>
</Item>
</Position>
<Qu>100</Qu>
</Ad>
</Action>
Change:
<xsl:value-of select="//Action/Ad/Position/Item/ID"/>
to:
<xsl:value-of select="Ad/Position/Item/ID"/>
What you have is an absolute path, starting from the root - and it will select every item ID in the entire document. What you need is a relative path, starting from the current Action.
Similarly, change:
<xsl:value-of select="//Action/Ad/Qu"/>
to:
<xsl:value-of select="Ad/Qu"/>
I know about GPSbabel and have used it for transforming GPX files into CSV that I can then import into Filemaker Pro for manipulation. However, I'd like to be able to import GPX files more simply & neatly into FMP and hoped I could write a XSL stylesheet to convert the GPX form of XML to the FMPXMLRESULT grammar.
I'm looking at GPX files from Garmin Foretrex, Dakota, Nuvi & GPSmap296 devices. My latest attempt at a XSL stylesheet (using snippets cribbed from various examples) looks like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="trkseg">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<PRODUCT BUILD="01-25-2011" NAME="FileMaker" VERSION="ProAdvanced 11.0v3"/>
<DATABASE DATEFORMAT="Yyyy-m-d" LAYOUT="" NAME="gpx_import_test.fp7" RECORDS="{#count}"
TIMEFORMAT="k:mm:ss "/>
<METADATA>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="ele" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="lat" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="lon" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="time_text" TYPE="TEXT"/>
</METADATA>
<RESULTSET FOUND="#count">
<xsl:for-each select="trkpt">
<ROW MODID="0" RECORDID="{position()}">
<COL>
<DATA>
<xsl:value-of select="lat"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="lon"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="ele"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="time_text"/>
</DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
When I try to use the above stylesheet, FMP complains "XML parsing error: invalid document structure".
I'd welcome any suggestions - I guess I'm making some very simple and obvious error.
regards
Rowland
Here is a snipped version of a typical GPX file from a Garmin Dakota 20 that I am trying to import.
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<gpx xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:wptx1="http://www.garmin.com/xmlschemas/WaypointExtension/v1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
creator="Dakota 20" version="1.1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1
http://www.topografix.com/GPX/1/1/gpx.xsd
http://www.garmin.com/xmlschemas/GpxExtensions/v3
http://www8.garmin.com/xmlschemas/GpxExtensionsv3.xsd
http://www.garmin.com/xmlschemas/WaypointExtension/v1
http://www8.garmin.com/xmlschemas/WaypointExtensionv1.xsd
http://www.garmin.com/xmlschemas/TrackPointExtension/v1
http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd">
<metadata>
<link href="http://www.garmin.com">
<text>Garmin International</text>
</link>
<time>2011-10-21T16:55:50Z</time>
</metadata>
<trk>
<name>Current Track: 20 OCT 2011 10:31</name>
<extensions>
<gpxx:TrackExtension>
<gpxx:DisplayColor>Black</gpxx:DisplayColor>
</gpxx:TrackExtension>
</extensions>
<trkseg>
<trkpt lat="51.6084605176" lon="-2.2182025295">
<ele>43.40</ele>
<time>2011-10-20T09:31:44Z</time>
</trkpt>
<trkpt lat="51.6084605176" lon="-2.2182025295">
<ele>47.24</ele>
<time>2011-10-20T09:31:53Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>
Several problems here. One is this:
<xsl:template match="trkseg">
You do not start at the root, so the XSLT processor will process the root and all other elements using default rules (which are to output the value of the element). This may add extra text around FMPXMLRESULT. To fix it we need to do something like:
<xsl:template match="/">
<xsl:for-each select="//trkseg/trkpt"> <!-- or use a full path -->
...
</xsl:for-each>
This is not enough because trkseg is not just trkseg, it's trkseg in the GPX namespace. Without proper namespace declaration the stylesheet won't find the elements at all. And the final problem is that lat and lon are attributes and you need to refer to them as #lat and #lon. Here's the working XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:gpx="http://www.topografix.com/GPX/1/1"
exclude-result-prefixes="gpx">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<PRODUCT BUILD="01-25-2011" NAME="FileMaker" VERSION="ProAdvanced 11.0v3"/>
<DATABASE DATEFORMAT="Yyyy-m-d"
LAYOUT="" NAME="gpx_import_test.fp7" RECORDS="{#count}"
TIMEFORMAT="k:mm:ss "/>
<METADATA>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="ele" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="lat" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="lon" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="time" TYPE="TEXT"/>
</METADATA>
<RESULTSET FOUND="count(//gpx:trkseg/gpx:trkpt)">
<xsl:for-each select="//gpx:trkseg/gpx:trkpt">
<ROW MODID="0" RECORDID="{position()}">
<COL>
<DATA>
<xsl:value-of select="#lat"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="#lon"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:ele"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:time"/>
</DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</xsl:template>
</xsl:stylesheet>
Here is a xslt file to read out only the waypoints, not the tracks: By
the way: I added the fields time, name, sym and type to the import. If
not needed, delete the corresponding lines in this file.
Copy and store the text below the dotted line in a file named
"yourchoice.xslt" where "yourchoice" might be any name you want and
use this as a xslt transformation file in filemaker.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:gpx="http://www.topografix.com/GPX/1/1"
exclude-result-prefixes="gpx">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<FMPXMLRESULT xmlns="http://www.filemaker.com/fmpxmlresult">
<ERRORCODE>0</ERRORCODE>
<PRODUCT BUILD="01-25-2011" NAME="FileMaker" VERSION="ProAdvanced 11.0v3"/>
<DATABASE DATEFORMAT="Yyyy-m-d"
LAYOUT="" NAME="gpx_import_test.fp7" RECORDS="{#count}"
TIMEFORMAT="k:mm:ss "/>
<METADATA>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="latitude" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="longitude" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="elevation" TYPE="NUMBER"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="time" TYPE="TEXT"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="name" TYPE="TEXT"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="sym" TYPE="TEXT"/>
<FIELD EMPTYOK="YES" MAXREPEAT="1" NAME="type" TYPE="TEXT"/>
</METADATA>
<RESULTSET FOUND="count(gpx:wpt)">
<xsl:for-each select="gpx:wpt">
<ROW MODID="0" RECORDID="{position()}">
<COL>
<DATA>
<xsl:value-of select="#lat"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="#lon"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:ele"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:time"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:name"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:sym"/>
</DATA>
</COL>
<COL>
<DATA>
<xsl:value-of select="gpx:type"/>
</DATA>
</COL>
</ROW>
</xsl:for-each>
</RESULTSET>
</FMPXMLRESULT>
</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>
Say I have the following XML:
<root>
<tokens>
<token ID="t1">blah</token>
<token ID="t2">blabla</token>
<token ID="t3">shovel</token>
</tokens>
<relatedStuff>
<group gID="s1">
<references tokID="t1"/>
<references tokID="t2"/>
</group>
<group gID="s2">
<references tokID="t3"/>
</group>
</relatedStuff>
</root>
Now, considering that a for-each loop for every token would be pretty inefficient and a bad idea, how would one go about using template matching, to transform this xml into the following?
<s id="everything_merged">
<tok id="t1" gID="s1" >blah</tok>
<tok id="t2" gID="s1" >blabla</tok>
<tok id="t3" gID="s2" >shovel</tok>
</s>
All I want from <s> is the "gID", the gID corresponding to the token in the <tokens>.
<xsl:for-each select="b:root/a:tokens/a:token">
<!-- and here some template matching -->
<xsl:attribute name="gID">
<xsl:value-of select="--correspondingNode's--#gID"/>
</xsl:attribute>
</xsl:for-each>
I'm pretty fuzzy on this sort of thing, so thank you very much for any help!
The following stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<s id="everything_merged">
<xsl:apply-templates select="/root/tokens/token" />
</s>
</xsl:template>
<xsl:template match="token">
<tok id="{#ID}" gID="{/root/relatedStuff/group[
references[#tokID=current()/#ID]]/#gID}">
<xsl:apply-templates />
</tok>
</xsl:template>
</xsl:stylesheet>
Applied to this input (corrected for well-formedness):
<root>
<tokens>
<token ID="t1">blah</token>
<token ID="t2">blabla</token>
<token ID="t3">shovel</token>
</tokens>
<relatedStuff>
<group gID="s1">
<references tokID="t1" />
<references tokID="t2" />
</group>
<group gID="s2">
<references tokID="t3" />
</group>
</relatedStuff>
</root>
Produces:
<s id="everything_merged">
<tok id="t1" gID="s1">blah</tok>
<tok id="t2" gID="s1">blabla</tok>
<tok id="t3" gID="s2">shovel</tok>
</s>
A solution using keys and pure "push-style:
<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:key name="kgIDfromTokId" match="#gID"
use="../*/#tokID"/>
<xsl:template match="tokens">
<s id="everything_merged">
<xsl:apply-templates/>
</s>
</xsl:template>
<xsl:template match="token">
<tok id="{#ID}" gID="{key('kgIDfromTokId', #ID)}">
<xsl:apply-templates/>
</tok>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<tokens>
<token ID="t1">blah</token>
<token ID="t2">blabla</token>
<token ID="t3">shovel</token>
</tokens>
<relatedStuff>
<group gID="s1">
<references tokID="t1" />
<references tokID="t2" />
</group>
<group gID="s2">
<references tokID="t3" />
</group>
</relatedStuff>
</root>
the wanted, correct result is produced:
<s id="everything_merged">
<tok id="t1" gID="s1">blah</tok>
<tok id="t2" gID="s1">blabla</tok>
<tok id="t3" gID="s2">shovel</tok>
</s>