I have an XML which has a field with Date-Time stamp as below:
<?xml version='1.0' encoding='UTF-8'?
<root>
<row>
<Code>A1</Code>
<ID>20202020</ID>
<Date-Changed>April 01, 2021, 1:13 pm</Date-Changed>
</row>
<row>
<Code>A1</Code>
<ID>20202020</ID>
<Date-Changed>April 01, 2021, 1:14 pm</Date-Changed>
</row>
</root>
I need to convert this to a format which the XSLT understands so that I can filter only the rows which have the latest Date-time stamp. I have figured out a way to filter on the records with the latest Date-time stamp. However, the Date-time stamp format in the XML is throwing the logic off where I am filtering on the latest changed record.
What can I do so that my XSLT can understand the Date-time format in the above XML and output the below XML?
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:xs="http://www.w3.org/2001/XMLSchema">
<row>
<Code>A1</Code>
<ID>1234ABCD</ID>
<Date-Changed>2021-04-01T13:14:00</Date-Changed>
</row>
</root>
My XSLT looks like below:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
<xsl:template match="/">
<root>
<xsl:for-each select="root">
<xsl:for-each-group select="row" group-by="(concat(ID, Code))">
<xsl:variable name="Mod_Dt">
<xsl:value-of select="max(current-group()/Date-Changed/xs:dateTime(.))"/>
</xsl:variable>
<row>
<Code>
<xsl:value-of select="(current-group()[Date-Changed=$Mod_Dt]/Code)"/>
</Code>
<ID>
<xsl:value-of select="(current-group()[Date-Changed=$Mod_Dt]/ID)"/>
</ID>
<Date-Changed>
<xsl:value-of select="(current-group()[Date-Changed=$Mod_Dt]/Date-Changed)"/>
</Date-Changed>
</row>
</xsl:for-each-group>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Any help will be greatly appreciated!
You can define a function to convert the given string to a valid dateTime - for example:
<xsl:function name="my:string-to-dateTime">
<xsl:param name="string"/>
<!-- extract components -->
<xsl:variable name="parts" select="tokenize($string, ',\s|\s|:')"/>
<!-- adjust -->
<xsl:variable name="m" select="index-of(('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'), substring($parts[1], 1, 3))" />
<xsl:variable name="H12" select="number($parts[4]) mod 12" />
<xsl:variable name="H" select="if($parts[6]='pm') then $H12 + 12 else $H12" />
<!-- output -->
<xsl:sequence select="xs:dateTime(concat($parts[3], format-number($m, '-00') , '-', $parts[2], format-number($H, 'T00'), format-number(number($parts[5]), ':00'), ':00'))" />
</xsl:function>
Demo: https://xsltfiddle.liberty-development.net/6qaHaQz/2
It looks as if it might be amenable to parsing using the parse-ietf-date() function in XSLT 3.0
Related
How should I convert dateTime in XSLT if I get the dateTime in GMT in below format
XML input:
<items>
<item>
<lastname>Lisa</lastname>
<firstname>Rimpell</firstname>
<checkintime>2017-02-05T05:40:00+03:00</checkintime>
<chekouttime>2017-02-05T10:40:00+03:00</chekouttime>
<address></address>
</item>
</items>
XSLT is:
<xsl:template match="/">
<Response>
<Data>
<xsl:call-template name="Buildusers" />
</Data>
</Response>
</xsl:template>
<xsl:template name="Buildusers">
<Rows>
<xsl:for-each select="//items/item">
<Row Action="ADD">
<xsl:value-of select="lastname" />
|<xsl:value-of select="firstname" />
|<xsl:value-of select="checkintime" />
|<xsl:value-of select="chekouttime" />
|<xsl:value-of select="address" />
</Row>
</xsl:for-each>
</Rows>
</xsl:template>
i need to build the rows like below with the conversion of 'dateTime. That is when I get the value of dateTime it should convert and build the row
Expected output:
Lisa|Rimpell|2017-02-05 02:40:00|2017-02-05 07:40:00||
In XSLT 2.0, you can do:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="http://www.example.com/my"
exclude-result-prefixes="xs my">
<xsl:output method="xml" indent="yes" version="1.0" encoding="UTF-8" />
<xsl:strip-space elements="*" />
<xsl:function name="my:adjust-dateTime">
<xsl:param name="dateTime"/>
<xsl:variable name="UTC" select="adjust-dateTime-to-timezone($dateTime, xs:dayTimeDuration('PT0H'))"/>
<xsl:sequence select="format-dateTime($UTC, '[Y0001]-[M01]-[D01] [H01]:[m01]:[s01]')" />
</xsl:function>
<xsl:template match="/items">
<Response>
<Data>
<Rows>
<xsl:for-each select="item">
<Row Action="ADD">
<xsl:value-of select="lastname, firstname, my:adjust-dateTime(checkintime), my:adjust-dateTime(chekouttime), address" separator="|" />
</Row>
</xsl:for-each>
</Rows>
</Data>
</Response>
</xsl:template>
</xsl:stylesheet>
to receive:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Data>
<Rows>
<Row Action="ADD">Lisa|Rimpell|2017-02-05 02:40:00|2017-02-05 07:40:00|</Row>
</Rows>
</Data>
</Response>
I have the following XML data:
<result>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<Rate>79.00</Rate>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RatePlanID>2</RatePlanID>
<RoomRatePlan>Standard</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
<row>
<CountryId>26</CountryId>
<CountryName>United Kingdom</CountryName>
<NoOfNights>1</NoOfNights>
<AccommodationID>6004</AccommodationID>
<RoomID>2</RoomID>
<RoomName>Double Room</RoomName>
<RatePlanID>1</RatePlanID>
<RoomRatePlan>Advance</RoomRatePlan>
<NoOfSameTypeRoom>0</NoOfSameTypeRoom>
<RoomSize/>
<Max_Person>2</Max_Person>
<RackRate>199</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<Rate>89.00</Rate>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</row>
</result>
My XSLT for the above xml is this:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes" method="xml" />
<xsl:strip-space elements="*"/>
<!-- Default template : ignore unrecognized elements and text -->
<xsl:template match="*|text()" />
<!-- Match document root : add hotels element and process each children node of result -->
<xsl:template match="/">
<hotels>
<!-- We assume that the XML documents are always going to follow the structure:
result as the root node and xml_acc elements as its children -->
<xsl:for-each select="result/row">
<result>
<hotel_rooms>
<xsl:element name="hotel_id">
<xsl:value-of select="AccommodationID"/>
</xsl:element>
<xsl:apply-templates />
</hotel_rooms>
<xsl:element name="Rate">
<xsl:element name="RoomRatePlan">
<xsl:value-of select="RoomRatePlan"/>
</xsl:element>
<xsl:element name="numeric_price">
<xsl:value-of select="Rate"/>
</xsl:element>
</xsl:element>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
<!-- Elements to be copied as they are -->
<xsl:template match="NoOfNights|RoomName|RoomSize|Max_Person|RackRate|RatePerDay|CurrencySymbol|NoOfRoomsAvailable|RoomDescription|RoomFacilities|PolicyComments|Breakfast|Policy|Message">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="Photo_Max60">
<RoomImages>
<Photo_Max60>
<xsl:value-of select="." />
</Photo_Max60>
<Photo_Max300>
<xsl:value-of select="../Photo_Max300" />
</Photo_Max300>
<Photo_Max500>
<xsl:value-of select="../Photo_Max500" />
</Photo_Max500>
</RoomImages>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0, I want to group by Room ID and Hotel ID . so in the above data, I Want the result like this.
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>£</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</result>
<hotels>
I want a xslt file for the above xml output i need.please help..
Muenchian method:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent="yes" />
<xsl:key name="room-per-hotel" match="result" use="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<xsl:template match="/">
<hotels>
<xsl:for-each select="hotels/result[count(. | key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))[1]) = 1]">
<xsl:sort select="concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID)" />
<result>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('room-per-hotel', concat(hotel_rooms/hotel_id, '-', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</result>
</xsl:for-each>
</hotels>
</xsl:template>
</xsl:stylesheet>
Working example
Well neither your input nor your wanted result is well-formed with tags not being closed properly and the existence of an entity reference £ to a not declared entity but if you want to group with XSLT 1.0 then have a look at the following XSLT 1.0 sample using Muenchian grouping:
<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="group" match="result" use="concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID)"/>
<xsl:template match="hotels">
<xsl:copy>
<xsl:apply-templates select="result[generate-id() = generate-id(key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))[1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="result">
<xsl:copy>
<xsl:copy-of select="hotel_rooms"/>
<Rate>
<xsl:copy-of select="key('group', concat(hotel_rooms/hotel_id, '|', hotel_rooms/RoomID))/Rate/*"/>
</Rate>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It transforms
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
</Rate>
</result>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize/>
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>5</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_89.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>
into
<hotels>
<result>
<hotel_rooms>
<hotel_id>6004</hotel_id>
<NoOfNights>1</NoOfNights>
<RoomID>1</RoomID>
<RoomName>Double for Sole Use</RoomName>
<RoomSize />
<Max_Person>1</Max_Person>
<RackRate>189</RackRate>
<CurrencySymbol>pound</CurrencySymbol>
<NoOfRoomsAvailable>4</NoOfRoomsAvailable>
<RatePerDay>27 Mar 2013_79.00</RatePerDay>
</hotel_rooms>
<Rate>
<RoomRatePlan>Advance</RoomRatePlan>
<numeric_price>79.00</numeric_price>
<RoomRatePlan>Standard</RoomRatePlan>
<numeric_price>89.00</numeric_price>
</Rate>
</result>
</hotels>
I want to:
select all attributes "foo", which are string values, and store the values in a list.
transform each value of attribute "foo" in this list using some map in my xslt to a number.
select the max value of the list and output that.
So given the following xml:
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
And the following mapping:
<xsl:variable name="myMap">
<entry key="A">1</entry>
<entry key="B">2</entry>
<entry key="C">3</entry>
</xsl:variable>
The output would be:
<max>3</max>
Another question, why can't I indent my code? I'm putting spaces but it's not working.
I This standard XSLT 1.0 transformation (most resembling your approach):
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:attribute name="foo">
<xsl:value-of select="$vMap[#key = current()]/#value"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document (as you didn't provide any):
<t foo="X">
<a foo="A">
<b foo="B"/>
</a>
<c foo="C"/>
</t>
produces the wanted, correct result:
<t foo="8">
<a foo="1">
<b foo="2"/>
</a>
<c foo="3"/>
</t>
Explanation: Appropriate use of the XSLT current() function.
II. XSLT 1.0 solution using keys for speed
<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="kValFromKey" match="entry/#value" use="../#key"/>
<xsl:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
<entry key="X" value="8"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#foo">
<xsl:variable name="vCur" select="."/>
<xsl:attribute name="foo">
<xsl:for-each select="document('')">
<xsl:value-of select="key('kValFromKey', $vCur)"/>
</xsl:for-each>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document (above), the same correct result is produced.
Explanation:
Use of <xsl:for-each select="document('')"> to set the current document to the stylesheet, so that the key() function will use the key index built for this document.
Saving the node matched by the template in a variable so that we can use it inside the xsl:for-each -- current() cannot be correctly used here, because it gets the current node on which xsl:for-each operates.
UPDATE: The OP has now clarified in a comment that his biggest problem is finding the maximum.
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:value-of select=
"$vFoosMapped
[not($vFoosMapped/#value > #value)]
/#value
"/>
</max>
</xsl:template>
</xsl:stylesheet>
When given this XML document (the one provided by the OP lacks a singlr top element):
<t>
<tag foo="A">apples</tag>
<tag foo="C">oranges</tag>
<tag foo="B">trees</tag>
</t>
the wanted, correct result is produced:
<max>3</max>
Remark: A more efficient way of calculating maximum (or minimum -- in a similar way) in XSLT 1.0 is to do this:
<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:variable name="vrtfMap">
<entry key="A" value="1"/>
<entry key="B" value="2"/>
<entry key="C" value="3"/>
</xsl:variable>
<xsl:variable name="vMap" select=
"document('')/*/xsl:variable[#name = 'vrtfMap']/*"/>
<xsl:template match="/">
<xsl:variable name="vDoc" select="."/>
<xsl:variable name="vFoosMapped"
select="$vMap[#key = $vDoc/*/*/#foo]"/>
<max>
<xsl:for-each select="$vFoosMapped">
<xsl:sort select="#value" data-type="number" order="descending"/>
<xsl:if test="position() = 1">
<xsl:value-of select="#value"/>
</xsl:if>
</xsl:for-each>
</max>
</xsl:template>
</xsl:stylesheet>
Another question, why can't I indent my code? I'm putting spaces but
it's not working.
This is a SO bug that they failed to fix for many months.
Most probably you are using IE. If your version is 9, then do the following:
Press F12.
In the window that pops up click on the right-most menu and select: "Document mode: IE9 Standards"
Now you should be able to see the code with indentation.
I got everything working (thank empo) except the ctrlname column. I don't know the syntax well enough. What I am trying to do is use the xslt to sort the xml in the gridview by the column name. Everything is working but the ctrlname column. How do I pass an attribute to the XSLT? I've tried: #name, Data/#name, Data[#name], ctrlname. Nothing works.
XmlDataSource1.EnableCaching = False
Dim xslTrnsform As System.Xml.Xsl.XsltArgumentList = New System.Xml.Xsl.XsltArgumentList
xslTrnsform.AddParam("sortby", "", sortAttr)
xslTrnsform.AddParam("orderas", "", orderby)
XmlDataSource1.TransformArgumentList = xslTrnsform
XmlDataSource1.DataFile = "~/App_LocalResources/DST_Test.xml"
XmlDataSource1.XPath = "//data"
XmlDataSource1.TransformFile = xsltFileName
'XmlDataSource1.DataBind()
GridView1.DataSource = XmlDataSource1
GridView1.DataBind()
XSL
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:param name="sortby"></xsl:param>
<xsl:param name="orderas"></xsl:param>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
XML input
<?xml version="1.0" encoding="utf-8" ?>
<root>
<data name="Test1.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
</data>
<data name="Test3.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
</data>
</root>
The currently accepted answer has one flaw: Whenever there is an attribute of data with the same name as a child element of data, the sort will always be performed using as keys the values of the attribute. Also, it is too long.
This solution solves the problem (and is shorter) allowing to specify whether the sort should be by attribute-name or by element-name:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="sortby" select="'attrib!name'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select=
"*[name()=substring-after($sortby, 'elem!')]
|
#*[name()=substring-after($sortby, 'attrib!')]"
data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data ctrlname="{#name}" value="{value}"
comment="{comment}"/>
</xsl:template>
</xsl:stylesheet>
When applied on this XML document (based on the provided one, but made a little-bit more interesting):
<root>
<data name="Test3.Text" xml:space="preserve">
<value>Please Pick Bare Pump</value>
<comment>Tab - Pump Configuration</comment>
<name>X</name>
</data>
<data name="Test2.Text" xml:space="preserve">
<value>Complete</value>
<comment>A07</comment>
<name>Z</name>
</data>
<data name="Test1.Text" xml:space="preserve">
<value>Confirmed</value>
<comment>A01</comment>
<name>Y</name>
</data>
</root>
the correct result (sorted by the name attribute) is produced:
<root>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
</root>
Now, replace the <xsl:param name="sortby" select="'attrib!name'"/> with:
<xsl:param name="sortby" select="'elem!name'"/>
and apply the transformation again on the same XML document. This time we get the result correctly sorted by the values of the child-element name:
<root>
<data ctrlname="Test3.Text" value="Please Pick Bare Pump" comment="Tab - Pump Configuration"/>
<data ctrlname="Test1.Text" value="Confirmed" comment="A01"/>
<data ctrlname="Test2.Text" value="Complete" comment="A07"/>
</root>
Explanation:
To distinguish whether we want to sort by an element-child or by an attribute, we use the convention that elem!someName means the sort must be by the values of a child element named someName. Similarly, attrib!someName means the sort must be by the values of an attribute named someName.
The <xsl:sort> insruction is modified accordingly to select as key correctly either an attribute or a child element. No ambiguity is allowed, because the starting substring of the sortby parameter now uniquely identifies whether the key should be an attribute or a child element.
Yes, I'm sorry didnt notice that you wanted also sort by attributes. Note also that you have changed the syntax of xsl:param and it's not correct in that way. It's very important that you keep the single quotes inside the double ones. Here is the final template:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:param name="sortby" select="'value'"/>
<xsl:param name="orderas" select="'ascending'"/>
<xsl:output method="xml" indent="yes"/>
<!--<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>-->
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby]|#*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
<xsl:template match="data">
<data>
<xsl:attribute name="ctrlname">
<xsl:value-of select="#name"/>
</xsl:attribute>
<xsl:attribute name="value">
<xsl:value-of select="value" />
</xsl:attribute>
<xsl:attribute name="comment">
<xsl:value-of select="comment" />
</xsl:attribute>
</data>
</xsl:template>
</xsl:stylesheet>
OK, I think this should work for you, allowing you to specify either attribute or element names in the $sortby parameter:
<xsl:template match="root">
<root>
<xsl:apply-templates select="data">
<xsl:sort select="*[name()=$sortby] | #*[name()=$sortby]" data-type="text" order="{$orderas}"/>
</xsl:apply-templates>
</root>
</xsl:template>
(you would just pass in "name" as the value of the $sortby parameter)
The problem was that the sort value node selection was only matching elements (* matches elements only).
I have a dateTime variable, and I want to convert it to a decimal value of epoch.
How can this be done?
I tried using:
seconds-from-duration($time, xs:dateTime('1970-01-01T00:00:00'))
but it just returns 0.
Please advice.
Thanks.
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="current-dateTime()"/>
<xsl:sequence select=
"( current-dateTime() - xs:dateTime('1970-01-01T00:00:00') )
div
xs:dayTimeDuration('PT1S')
"/>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the wanted result -- the current date-time and its Unix epoch (the number of seconds since 1/1/1970 ):
2010-08-12T06:26:54.273-07:00 1281594414.273
A pure xsl 1.0 lib example:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="https://github.com/ilyakharlamov/pure-xsl/date"
version="1.0">
<xsl:import href="https://raw.github.com/ilyakharlamov/pure-xsl/master/date.xsl"/>
<xsl:template match="/">
<xsl:variable name="time_as_timestamp" select="1365599995640"/>
<xsl:text>time_as_timestamp:</xsl:text><xsl:value-of select="$time_as_timestamp"/><xsl:text>
</xsl:text>
<xsl:variable name="time_as_xsdatetime">
<xsl:call-template name="date:date-time">
<xsl:with-param name="timestamp" select="$time_as_timestamp"/>
</xsl:call-template>
</xsl:variable>
<xsl:text>time_as_xsdatetime:</xsl:text><xsl:value-of select="$time_as_xsdatetime"/><xsl:text>
</xsl:text>
<xsl:text>converted back:</xsl:text>
<xsl:call-template name="date:timestamp">
<xsl:with-param name="date-time" select="$time_as_xsdatetime"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Output:
time_as_timestamp:1365599995640
time_as_xsdatetime:2013-04-10T13:19:55.640Z
converted back:1365599995640
As an xpath which does not use division but extracts from the duration:
for $i in (current-dateTime()-xs:dateTime('1970-01-01T00:00:00Z'))
return ((days-from-duration($i)*86400)+(hours-from-duration($i)*3600)+(minutes-from-duration($i)*60)+(seconds-from-duration($i)))