Regarding Umbraco XSLT version 1.
I have aprox. 150 news items in XML. Lets say like this (all is pseudocode until I get more familiar with this xml/xslt):
<news>
<data alias=date>2008-10-20</data>
</news>
<news>
<data alias=date>2009-11-25</data>
</news><news>
<data alias=date>2009-11-20</data>
</news> etc. etc....
I would like to run through the XML and create html-output as a news archive. Something like (tags not important):
2008
Jan
Feb
...
2009
Jan
Feb
Mar
etc. etc.
I can only come up with a nested for-each (pseudocode):
var year_counter = 2002
var month_counter = 1
<xsl:for-each select="./data [#alias = 'date']=year_counter">
<xsl:for-each select="./data [#alias = 'date']=month_counter">
<xsl:value-of select="data [#alias = 'date']>
"...if month_counter==12 end, else month_counter++ ..."
</xsl:for-each>
"... year_counter ++ ..."
</xsl:for-each>
But a programmer pointet out that looping through 10 years will give 120 loops and that is bad coding. Since I think Umbraco caches the result I am not so concerned, plus in this case there will be a max. of 150 records.
Any clues on how to sort and output many news items and group them in year and group each year in months?
Br. Anders
For the following solution I used this XML file:
<root>
<news>
<data alias="date">2008-10-20</data>
</news>
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
<news>
<data alias="date">2009-03-20</data>
</news>
<news>
<data alias="date">2008-01-20</data>
</news>
</root>
and this XSLT 1.0 transformation:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:cfg="http://tempuri.org/config"
exclude-result-prefixes="cfg"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- index news by their "yyyy" value (first 4 chars) -->
<xsl:key
name="kNewsByY"
match="news"
use="substring(data[#alias='date'], 1, 4)"
/>
<!-- index news by their "yyyy-mm" value (first 7 chars) -->
<xsl:key
name="kNewsByYM"
match="news"
use="substring(data[#alias='date'], 1, 7)"
/>
<!-- translation table (month number to name) -->
<config xmlns="http://tempuri.org/config">
<months>
<month id="01" name="Jan" />
<month id="02" name="Feb" />
<month id="03" name="Mar" />
<month id="04" name="Apr" />
<month id="05" name="May" />
<month id="06" name="Jun" />
<month id="07" name="Jul" />
<month id="08" name="Aug" />
<month id="09" name="Sep" />
<month id="10" name="Oct" />
<month id="11" name="Nov" />
<month id="12" name="Dec" />
</months>
</config>
<xsl:template match="root">
<xsl:copy>
<!-- group news by "yyyy" -->
<xsl:apply-templates mode="year" select="
news[
generate-id()
=
generate-id(key('kNewsByY', substring(data[#alias='date'], 1, 4))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- year groups will be enclosed in a <year> element -->
<xsl:template match="news" mode="year">
<xsl:variable name="y" select="substring(data[#alias='date'], 1, 4)" />
<year num="{$y}">
<!-- group this year's news by "yyyy-mm" -->
<xsl:apply-templates mode="month" select="
key('kNewsByY', $y)[
generate-id()
=
generate-id(key('kNewsByYM', substring(data[#alias='date'], 1, 7))[1])
]
">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</year>
</xsl:template>
<!-- month groups will be enclosed in a <month> element -->
<xsl:template match="news" mode="month">
<xsl:variable name="ym" select="substring(data[#alias='date'], 1, 7)" />
<xsl:variable name="m" select="substring-after($ym, '-')" />
<!-- select the label of the current month from the config -->
<xsl:variable name="label" select="document('')/*/cfg:config/cfg:months/cfg:month[#id = $m]/#name" />
<month num="{$m}" label="{$label}">
<!-- process news of the current "yyyy-mm" group -->
<xsl:apply-templates select="key('kNewsByYM', $ym)">
<xsl:sort select="data[#alias='date']" order="descending" />
</xsl:apply-templates>
</month>
</xsl:template>
<!-- for the sake of this example, news elements will just be copied -->
<xsl:template match="news">
<xsl:copy-of select="." />
</xsl:template>
</xsl:stylesheet>
When the transformation is applied, the following output is produced:
<root>
<year num="2009">
<month num="11" label="Nov">
<news>
<data alias="date">2009-11-25</data>
</news>
<news>
<data alias="date">2009-11-20</data>
</news>
</month>
<month num="03" label="Mar">
<news>
<data alias="date">2009-03-20</data>
</news>
</month>
</year>
<year num="2008">
<month num="10" label="Oct">
<news>
<data alias="date">2008-10-20</data>
</news>
</month>
<month num="01" label="Jan">
<news>
<data alias="date">2008-01-20</data>
</news>
</month>
</year>
</root>
It has the right structure already, you can adapt actual appearance to your own needs.
The solution is a two-phase Muenchian grouping approach. In the first phase, news items are grouped by year, in the second phase by year-month.
Please refer to my explanation of <xsl:key> and key() over here. You don't need to read the other question, though it is a similar problem. Just read the lower part of my answer.
What you need is the so-called Muenchian Grouping method, which addresses exactly this problem/pattern for XSLT.
Basically, it groups by finding unique keys and looping over the entries contained in the key being used.
in addition to lucero, check out Xsl grouping duplicates problem for avoiding problems with month names being removes
You can't do month_counter++ in XSLT, it's not a procedural language and it's not how XSLT works. So, it's kind of pointless to worry about this being inefficient if this does not work this way.
This looks like a major pain in the neck in XSLT. My XSLT is not fresh enough to try and actually implement it. But here are two ways:
1)
use xsl:key to extract all unique years-
then iterate through these years. For each year do
use xsl:key to extract all months
For each month do
2) (seems easier, if it works.)
sort them by date, save sorted array in variable
iterate this variable (it's important that variable holds sorted array)
each time look at preceding-sibling. If its year/month not equal to the current element, write the appropriate header
3) Forget XSLT, use a real programming language.
Related
I am struggling with XSLT and have been for days. I'm in the process of modifying a previous coworkers python code that transforms many different JSON files into xml and finally into kml. I'm within a hairs breadth of wrapping this up and, of course, the one part I can't get my head around is what's left.
I have an xml file with this structure:
<?xml version="1.0" ?>
<document xmlns="http://ws.wso2.org/dataservice">
<group>
<display_name>Housing</display_name>
<id>Housing</id>
<item>
<id>5063</id>
<image_url>images/5063.jpg</image_url>
<latitude>40.354007</latitude>
<longitude>-74.666675</longitude>
<name>Stanworth Apartments</name>
</item>
.
. (Many items omitted)
.
</group>
<group>
<display_name>Buildings</display_name>
<id>Building</id>
<item>
<id>5025</id>
<image_url>images/5025.jpg</image_url>
<latitude>40.350066</latitude>
<longitude>-74.603464</longitude>
<name>Lyman Spitzer Building</name>
<name_alt>LSB</name_alt>
<organization_id>ORG418</organization_id>
</item>
.
. (Many items omitted)
.
</group>
<group>
.
. (Many groups omitted)
.
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Entryway</id>
<item>
<description>Accessible entryway</description>
<id>E028</id>
<latitude>40.349159</latitude>
<longitude>-74.658629</longitude>
<name>E028</name>
</item>
<item>
<description>Accessible entryway</description>
<id>E029</id>
<latitude>40.349398</latitude>
<longitude>-74.658517</longitude>
<name>E029</name>
</item>
</group>
<group>
<display_name>Accessible Features</display_name>
<id>Route</id>
<item>
<description>Accessible pathway</description>
<id>R054</id>
<name>R054</name>
<steps>-74.66032495749012,40.3489269473544</steps>
<steps>-74.6602836233495,40.34888813533125</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R055</id>
<name>R055</name>
<steps>-74.66023036637355,40.34884827131961</steps>
<steps>-74.66018651597699,40.34881015960344</steps>
</item>
<item>
<description>Accessible pathway</description>
<id>R072</id>
<name>R072</name>
<steps>-74.66101885775542,40.34737535360176</steps>
<steps>-74.6610915120654,40.34740600913134</steps>
<steps>-74.66187000551304,40.34717392492537</steps>
</item>
</group>
</document>
Each "group" is transformed into a Folder in the final KML file.
<Folder id="Housing">
<name>Housing</name>
<Placemark id="_0288">
.
. (Many lines omitted)
.
The goal is to create one Folder "id='Accessible" with the contents of two groups. The group with id='Entryway' and the group with id='Route. The desired output would be:
<Folder id="Accessible">
<name>Accessible Features</name>
<Placemark id="_E001">
<name>E001</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.663266, 40.348289,0</coordinates>
</Point>
</Placemark>
<Placemark id="_E002">
<name>E002</name>
<description><![CDATA[<div><p>Accessible entryway</p></div>]]></description>
<styleUrl>#entryway</styleUrl>
<Point>
<coordinates>-74.662252, 40.348057,0</coordinates>
</Point>
</Placemark>
.
. then have the items from the group with id='Route'
.
<Placemark id="_R002">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135187837255,40.34699608960065
-74.65134698312161,40.34698651192196
</coordinates>
</LineString>
</Placemark>
<Placemark id="_R003">
<name>Accessible Routes</name>
<description><![CDATA[<div><p>Accessible pathway</p></div>]]></description>
<styleUrl/>
<Style>
<LineStyle>
<color>FFFF0000</color>
<width>4</width>
</LineStyle>
</Style>
<LineString>
<coordinates>
-74.65135184561255,40.34699603789065
-74.65134698312256 44.34698634192100
</coordinates>
</LineString>
</Placemark>
.
. more than 66,000 lines omitted
.
</Folder>
I've written the XSLT to transform the XML into these KML Folders and the only thing left to do is get them under the same folder.
What I've been trying to do is move all of the items from the group with id='Route' into the group with id='Entryway.
In my xslt file is an apply-templates at the group nodes.
<xsl:apply-templates select="ds:group">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
This is picked up by the template match for each group.
<xsl:template match="ds:group">
At which point I'm lost. I'll post my code but it is only going to confuse and depress you.
<xsl:template match="ds:group">
<xsl:choose>
<xsl:when test="not(ds:id = 'Route') and not(ds:id = 'Entryway')">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Entryway'">
<Folder id='Accessible'>
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:when>
<xsl:when test="ds:id = 'Route'">
<!-- Copy all of current node to Entryway node -->
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I don't think I'm going about this the right way. By the time the XSLT process gets to the group with id='Route' the KML Folder for Entryway has already been written. I'm at a dead end. Can I union two groups together based on the value of "id"? Conceptually the idea would be: <xsl:template match="ds:id='Route' | ds:id='Entryway'"> But that doesn't even compile.
Can I copy all of the elements of the group (id='Route') to the group (id='Entryway') after the first group has been processed?
Thank you in advance for your time and attention.
George
I think you need to "step" in at the document level and do e.g.
<xsl:template match="ds:document">
<Folder id='Accessible'>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
<!-- process other ds:group here as well e.g.
<xsl:apply-templates select="ds:group[not(ds:id = 'Entryway' or ds:id = 'Route')]"/>
-->
</xsl:template>
AFAICT you want to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="http://ws.wso2.org/dataservice"
exclude-result-prefixes="ns0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/ns0:document">
<Folders>
<xsl:variable name="accessible" select="ns0:group[ns0:id='Entryway' or ns0:id ='Route']" />
<xsl:if test="$accessible">
<Folder id="Accessible">
<name>Accessible Features</name>
<xsl:apply-templates select="$accessible/ns0:item"/>
</Folder>
</xsl:if>
<xsl:apply-templates select="ns0:group[not(ns0:id='Entryway' or ns0:id ='Route')]"/>
</Folders>
</xsl:template>
<xsl:template match="ns0:group">
<Folder id="{ns0:id}">
<name>
<xsl:value-of select="display_name"/>
</name>
<xsl:apply-templates select="ns0:item"/>
</Folder>
</xsl:template>
<xsl:template match="ns0:item">
<Placemark id="{ns0:id}">
<name>
<xsl:value-of select="ns0:name"/>
</name>
<!-- more here -->
</Placemark>
</xsl:template>
</xsl:stylesheet>
<xsl:template match="/ds:document">
<kml>
<Document>
<name>Campus Map</name>
<xsl:apply-templates select="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<xsl:sort select="ds:display_name"/>
</xsl:apply-templates>
<Folder id='Accessible'>
<name>Accessible Feature</name>
<xsl:apply-templates select="ds:group[ds:id = 'Entryway' or ds:id = 'Route']"/>
</Folder>
</Document>
</kml>
</xsl:template>
<xsl:template match="ds:group[ds:id = 'Entryway' or ds:id = 'Route']">
<xsl:apply-templates select="ds:item"/>
</xsl:template>
<xsl:template match="ds:group[ds:id != 'Entryway' and ds:id != 'Route']">
<Folder id="{ds:id}">
<name>
<xsl:value-of select="ds:display_name"/>
</name>
<xsl:apply-templates select="ds:item">
<xsl:sort select="ds:name"/>
</xsl:apply-templates>
</Folder>
</xsl:template>
XSL stylesheet is generating repeated output. Below is an example of it. The same thing is repeated thrice. In the first set of xml, i am only getting value of first attribute and in the secound from second attribute and so on.
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="" name="DOKUMENTENART"/>
</obj>
<obj>
<desc value=""/>
<index value="" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
I have also tired with xsl when and choose but the output was same. below is an example input xml with some attributes.
<?xml version = "1.0" encoding = "utf-8"?>
<root>
<document>
<field level = "document" name = "Fertigungsauftragsnummer" value = "113662176"/>
<field level = "document" name = "Materialnummer" value = "66260383180"/>
<field level = "document" name = "Dokumentenart" value = "Fertigungsauftrag"/>
</document>
</root>
below is the xsl style sheet which i am using for conversion. In the xsl template if i use match="/*" i dont get repeated output neither do i get values of xml attributes. It seems that for every xsl if we have one specific output. How can I make xsl style sheet to compile only once the input xml for all the xsl if statements?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/document/*">
<xsl:text>
</xsl:text><xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>desc value="</xsl:text>
<xsl:if test="#name='Fertigungsauftragsnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Materialnummer'">
<xsl:value-of select="#value" />
<xsl:if test="#name='Dokumentenart'">
<xsl:value-of select="#value" />
</xsl:if>
</xsl:if>
</xsl:if>
<xsl:text disable-output-escaping="yes">"/></xsl:text><xsl:text>
</xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text><xsl:text>/obj</xsl:text><xsl:text disable-output-escaping="yes">></xsl:text><xsl:text>
</xsl:text>
</xsl:template>
</xsl:transform>
Expected output is shown below
<?xml version="1.0" encoding="UTF-8"?>
<obj>
<desc value="113662176"/>
<index value="66260383180" name="MATERIALNUMMER"/>
<index value="Fertigungsauftrag" name="DOKUMENTENART"/>
</obj>
You current approach of using xsl:text with disable-output-escaping is not the right approach when building XML. You should be writing out the XML tags directly. It looks like you want to convert document elements into obj elements so you should have a template like this
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
(In your current XSLT, you were converting field into obj which is why you got three of them).
It also looks like you want fields with name "Fertigungsauftragsnummer" into a desc element, so just create a template for that.
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
Note the syntax for the attributes using curly braces. These are known as Attribute Value Templates.
For the other two fields, you can share a common template, as it looks like you just want to make the name upper-case in both cases
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
Try this XSLT
<xsl:stylesheet version = "1.0" xmlns:xsl = "http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/root/document">
<obj>
<xsl:apply-templates select="field" />
</obj>
</xsl:template>
<xsl:template match="field[#name='Fertigungsauftragsnummer']">
<desc value="{#value}"/>
</xsl:template>
<xsl:template match="field">
<index value="{#value}" name="{translate(#name, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"/>
</xsl:template>
</xsl:stylesheet>
This is the source XML:
<root>
<!-- a and b have the same date entries, c is different -->
<variant name="a">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="b">
<booking>
<date from="2017-01-01" to="2017-01-02" />
<date from="2017-01-04" to="2017-01-06" />
</booking>
</variant>
<variant name="c">
<booking>
<date from="2017-04-06" to="2017-04-07" />
<date from="2017-04-07" to="2017-04-09" />
</booking>
</variant>
</root>
I'd like to group the three variants so that each variants with same #from and #to in each date should be grouped together.
My attempt is:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"></xsl:output>
<xsl:template match="root">
<variants>
<xsl:for-each-group select="for $i in variant return $i" group-by="booking/date/#from">
<group>
<xsl:attribute name="cgk" select="current-grouping-key()"/>
<xsl:copy-of select="current-group()"></xsl:copy-of>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
</xsl:stylesheet>
But this gives too many groups. (How) is this possible to achieve?
Using a composite key and XSLT 3.0 you could use
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="booking/date/(#from, #to)" composite="yes">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
which should group any variant elements together which have the same descendant date element sequence.
XSLT 3.0 is supported by Saxon 9.8 (any edition) or 9.7 (PE and EE) or a 2017 release of Altova XMLSpy/Raptor.
Using XSLT 2.0 you could concatenate all those date values with string-join():
<xsl:template match="root">
<variants>
<xsl:for-each-group select="variant" group-by="string-join(booking/date/(#from, #to), '|')">
<group key="{current-grouping-key()}">
<xsl:copy-of select="current-group()"/>
</group>
</xsl:for-each-group>
</variants>
</xsl:template>
Like the XSLT 3.0 solution, it only groups variant with the same sequence of date descendants, I am not sure whether that suffices or whether you might want to sort any date descendants first before computing the grouping key. In the XSLT 3 case you could do that easily with
<xsl:for-each-group select="variant" group-by="sort(booking/date, (), function($d) { xs:date($d/#from), xs:date($d/#to) })!(#from, #to)" composite="yes">
inline (although that leaves 9.8 HE behind as it does not support function expressions/higher order functions, so there you would need to move the sorting to your own user-defined xsl:function and in there use xsl:perform-sort).
I have following xml
<Report>
<Items>
<Item>
<Id>1</Id>
<TotalSent>251</TotalSent>
<Opened>48</Opened>
<LastSend>01/07/2013 16:38:18</LastSend>
<Bounced>1</Bounced>
<Unopened>202</Unopened>
</Item>
</Items>
</Report>
i want to transform it to another xml using xslt , my desired o/p is like below
<chart subcaption ="Last sent on Monday 01 July 2013 at 16:38">
<set label="Opened" value="48"/>
<set label="Bounced" value="1"/>
</chart>
I am not able to get date as i want for subcaption attribute.
I tried below xslt code but it is not working
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
<xsl:template match="/">
<chart>
<xsl:variable name='lastSend' select='Report/Items/Item/LastSend' />
<xsl:attribute name="subcaption">
<xsl:value-of select="ms:format-date($lastSend, ' Last sent on MMM dd, yyyy at')"/>
<xsl:value-of select="ms:format-time($lastSend, ' hh:mm')"/>
</xsl:attribute>
<xsl:for-each select="Report/Items/Item">
<set>
<xsl:attribute name="label">Opened</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="Opened" />
</xsl:attribute>
</set>
<set>
<xsl:attribute name="label">Bounced</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="Bounced" />
</xsl:attribute>
</set>
</xsl:for-each>
</chart>
</xsl:template>
</xsl:stylesheet>
when i am passing hard coded value in ms:format-date() & ms:format-time() functions, like 01/07/2013 16:38:18 it was working fine , but when i am passing variable value $lastSend it is not working.
Note: I can use any version of xsl.
If you want to use XSLT 2.0 then you need to convert your custom date respectively dateTime format into an xs:dateTime and then you can use the format-dateTime function that XSLT 2.0 provides (see http://www.w3.org/TR/xslt20/#format-date):
<xsl:template match="LastSend">
<!-- 01/07/2013 16:38:18 -->
<xsl:variable name="dt" as="xs:dateTime" select="xs:dateTime(concat(substring(., 7, 4), '-', substring(., 4, 2), '-', substring(., 1, 2), 'T', substring(., 12)))"/>
<xsl:attribute name="subcaption" select="format-dateTime($dt, 'Last sent on [F] [D01] [MNn] [Y0001] at [H01]:[m01]')"/>
</xsl:template>
Take the above second argument "picture string" as an example on how to format a dateTime, you might need to adjust it for your needs, based on the picture string arguments documented in the XSLT 2.0 specification.
I have two tags in the input file, variable and type:
<variable baseType="int" name="X">
</variable>
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
And I need to generate the following output file:
<Item name="Y">
<Field name="X" type="Long" />
</Item>
So conceptually my approach here has been to convert the type tag into the Item tage, the variable instance to the Field. That's working fine:
<xsl:for-each select="type[#baseType='structure']">
<Item>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:for-each select="variableInstance">
<Field>
<xsl:attribute name="name"><xsl:value-of select="#name" /></xsl:attribute>
<xsl:attribute name="type">**THIS IS WHERE I'M STUCK**</xsl:attribute>
</Field>
</xsl:for-each>
</Item>
</xsl:for-each>
The problem I'm stuck on is:
I don't know how to get the variableInstance/Field tag to match on the variable tag by name, so I can access the baseType.
I need to map "int" to "Long" once I'm able to do 1.
Thanks in advance!
PROBLEM 1.
For the first problem that you have you can use a key:
<xsl:key name="variable-key" match="//variable" use="#name" />
That key is going to index all variable elements in the document, using their name. So now, we can access any of those elements by using the following XPath expression:
key('variable-key', 'X')
Using this approach is efficient when you have a lot of variable elements.
NOTE: this approach is not valid if each variable has its own scope (i.e. you have local variables which are not visible in different parts of the document). In that case this approach should be modified.
PROBLEM 2.
For mapping attributes you could use a template like the following:
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
The meaning of this transformation is: each time that we match a baseType attribute with int value, it has to be replaced by a Long value.
This transformation would be in place for each #baseType attribute in the document.
Using the described strategies a solution could be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<!-- Index all variable elements in the document by name -->
<xsl:key name="variable-key"
match="//variable"
use="#name" />
<!-- Just for demo -->
<xsl:template match="text()" />
<!-- Identity template: copy attributes by default -->
<xsl:template match="#*">
<xsl:copy>
<xsl:value-of select="." />
</xsl:copy>
</xsl:template>
<!-- Match the structure type -->
<xsl:template match="type[#baseType='structure']">
<Item>
<xsl:apply-templates select="*|#*" />
</Item>
</xsl:template>
<!-- Match the variable instance -->
<xsl:template match="variableInstance">
<Field>
<!-- Use the key to find the variable with the current name -->
<xsl:apply-templates select="#*|key('variable-key', #name)/#baseType" />
</Field>
</xsl:template>
<!-- Ignore attributes with baseType = 'structure' -->
<xsl:template match="#baseType[. = 'structure']" />
<!-- Change all baseType attributes with long values to an attribute
with the same name but with an int value -->
<xsl:template match="#baseType[. = 'int']">
<xsl:attribute name="baseType">
<xsl:value-of select="'Long'" />
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
That code is going to transform the following XML document:
<!-- The code element is present just for demo -->
<code>
<variable baseType="int" name="X" />
<type baseType="structure" name="Y">
<variableInstance name="X" />
</type>
</code>
into
<Item name="Y">
<Field baseType="Long" name="X"/>
</Item>
Oki I've got a solution for point 1 xsltcake slice or with use of templates. For point two I would probably use similar template to the one that Pablo Pozo used in his answer