Sorting elements by edited attribute - xslt

I am trying to merge two XML files "a.xml" and "b.xml" into a HTML table using XSLT 1.0. Both files contain elements called "event" that each have a "time" attribute with a dateTime value attached to them. I want the HTML table to be sorted chronologically. Whereas the time attributes of file "a.xml" are formated correctly (CCYY-MM-DDTHH:MM:SS.msmsms), the time attributes of "b.xml" are not (CCYY-DDDTHH:MM:SS.msmsmsZ) and thus I am using some concats and substring functions to construct the correct format for the "time" attributes of the "b.xml" elements. My question is now: How can I use the original "time" attributes of "a.xml" and the corrected attributes of "b.xml" for sorting the rows of the HTML table?
I already tried using parameters for storing the correctly formated "time" attributes. I also tried using node-sets to tackle the issue in two steps (i.e. converting "b.xml" attributes first, saving result and then creating the HTML from the intermediate file), but neither of these two ways worked for me. Lastly I tried sorting the HTML table on load of the page with a JavaScript, but the table is too big for doing it this way on each page load.
I am happy about every hint on a functionality of XSLT that could help me. I have to stick with XSLT1.0, though and can't use XSLT2.0 for this project.
a.xml
<?xml version="1.0" encoding="UTF-8"?>
<data>
<event time="2019-02-03T06:00:00.000"></event>
<event time="2019-02-01T06:00:00.000"></event>
</data>
b.xml before formating
<?xml version="1.0" encoding="UTF-8"?>
<data>
<event time="2019-035T06:00:00.000"></event>
<event time="2019-033T06:00:00.000"></event>
</data>
b.xml after formating
<?xml version="1.0" encoding="UTF-8"?>
<data>
<event time="2019-02-04T06:00:00.000"></event>
<event time="2019-02-02T06:00:00.000"></event>
</data>
current transform.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<table>
<xsl:apply-templates select="/Adata/event|document('b.xml')/Bdata/event">
<xsl:sort select="#time"/>
</xsl:apply-templates>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Bdata/event">
<!--Here I have some long operation to change the date format and save it as parameter "correctFormat"-->
<xsl:attribute name="time">
<xsl:value-of select="$correctFormat"/>
</xsl:attribute>
</xsl:template>
<xsl:template match="//event">
<tr>
<td><xsl:value-of select="#time"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
expected output out.html
<html>
<body>
<table>
<tr><td>2019-02-01T06:00:00.000</td></tr>
<tr><td>2019-02-02T06:00:00.000</td></tr>
<tr><td>2019-02-03T06:00:00.000</td></tr>
<tr><td>2019-02-04T06:00:00.000</td></tr>
</table>
</body>
</html>
EDIT
Date conversion code
As requested I share as well my code for the conversion. I am using the exslt dates and time namespace by adding inside the header
<xsl:template match="data/event">
<xsl:param name="daysToAdd" select="concat('P',substring(#time,6,3),'D')"/>
<xsl:param name="startOfYear" select="concat(substring(#time,1,4),'-01-00')"/>
<xsl:param name="formatedDate">
<xsl:call-template name="date:add">
<xsl:with-param name="date-time" select="$startOfYear" />
<xsl:with-param name="duration" select="$daysToAdd" />
</xsl:call-template>
<xsl:value-of select="substring(#time,9,13)"/>
</xsl:param>
<xsl:copy>
<xsl:attribute name="time">
<xsl:value-of select="$formatedDate"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>

Consider the following example (minimized to the problem presented in your question):
XML
<data>
<event time="2019-02-03T06:00:00.000"></event>
<event time="2019-02-01T06:00:00.000"></event>
</data>
b.xml
<data>
<event time="2019-035T06:00:00.000"></event>
<event time="2019-033T06:00:00.000"></event>
</data>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/data">
<!-- CONVERT B -->
<xsl:variable name="b">
<xsl:for-each select="document('b.xml')/data/event">
<xsl:copy>
<xsl:attribute name="time">
<!-- the missing conversion part goes here -->
</xsl:attribute>
</xsl:copy>
</xsl:for-each>
</xsl:variable>
<!-- OUTPUT -->
<xsl:copy>
<xsl:for-each select="event | exsl:node-set($b)/event">
<xsl:sort select="#time" data-type="text" order="ascending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<data>
<event time="2019-02-01T06:00:00.000"/>
<event time="2019-02-02T06:00:00.000"/>
<event time="2019-02-03T06:00:00.000"/>
<event time="2019-02-04T06:00:00.000"/>
</data>

Related

Filemaker xml output via xslt with column names

I am new to xslt programming and xlm. I have created the code below this works fine, except that instead variable names for each column, it just shows "colno" How do I get the column names into the output?
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fmp="http://www.filemaker.com/fmpxmlresult"
exclude-result-prefixes="fmp"
>
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:variable name="kMetaData" select="fmp:METADATA/fmp:FIELD"/>
<xsl:variable name="colno"
select="count($kMetaData[following-sibling::fmp:FIELD/#NAME]) + 1" />
<xsl:template match="/fmp:FMPXMLRESULT">
<PERSON>
<xsl:apply-templates select="fmp:RESULTSET/fmp:ROW" />
</PERSON>
</xsl:template>
<xsl:template match="fmp:ROW">
<ELEMENTS>
<xsl:apply-templates select="fmp:COL" />
</ELEMENTS>
</xsl:template>
<xsl:template match="fmp:COL">
<xsl:element name="colno">
<xsl:value-of select="fmp:DATA" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
It is hard to make some suggestion without input xml. But at first sight this <xsl:element name="colno"> says "output an element <colno>". I think you should use something like <xsl:element name="{xpath/to/columnName}">
edit:
According to your input xml your template for "COL" element should look like
<xsl:template match="COL">
<xsl:variable name="colPosition" select="position()" />
<!-- Prevent spaces in NAME attribute of FIELD element -->
<xsl:variable name="colName" select="translate($kMetaData[$colPosition]/#NAME, ' ', '_')" />
<xsl:element name="{$colName}">
<xsl:value-of select="DATA"/>
</xsl:element>
</xsl:template>
Then the output looks like
<?xml version="1.0" encoding="utf-8"?>
<PERSON>
<ELEMENTS>
<FIRSTNAME>Richard</FIRSTNAME>
<LASTNAME>Katz</LASTNAME>
<MIDDLENAME>David</MIDDLENAME>
<REQUESTDT>1/1/2001</REQUESTDT>
<salutation>Mr</salutation>
<Bargaining_Unit>CSEA (02,03,04)</Bargaining_Unit>
<Field_134>b</Field_134>
</ELEMENTS>
</PERSON>

XSLT multiple stylesheets

I have the following xml
<TopLevel>
<data m="R263">
<s ut="263firstrecord" lt="2013-02-16T09:21:40.393" />
<s ut="263secondrecord" lt="2013-02-16T09:21:40.393" />
</data>
<data m="R262">
<s ut="262firstrecord" lt="2013-02-16T09:21:40.393" />
<s ut="262secondrecord" lt="2013-02-16T09:21:40.393" />
</data>
</TopLevel>
I have some XSLT that does the call template but it's not itterating correctly.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:value-of select="#m" />
<xsl:variable name="vYourName" select="#m"/>
<xsl:choose>
<xsl:when test="#m='R262'">
<xsl:call-template name="R262"/>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="#m='R263'">
<xsl:call-template name="R263"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="R262">
<xsl:for-each select="/TopLevel/data/s">
Column1=<xsl:value-of select="#ut" />
Column2=<xsl:value-of select="#lt" />
</xsl:for-each>
</xsl:template>
<xsl:template name="R263">
<xsl:for-each select="/TopLevel/data/s">
Column1=<xsl:value-of select="#ut" />
Column2=<xsl:value-of select="#lt" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This gives me 8 records insead of the 4 (<s> level) records. I know it has to do with my iteration ... but I am not sure how to address this.
I am also aware of the apply stylesheets but I couldn't unravel that mystery either ... If someone can help me with XSLT that will only process everything from <TopLevel> to <\TopLevel> checking the value of m at the <data> level and applying the stylesheet at the <s> level for each <s> record I will be greateful beyond belief.
I don't know what output you want to produce, but I suspect you want to replace
<xsl:for-each select="/TopLevel/data/s">
by
<xsl:for-each select="s">
that is, you only want to process the "s" elements within the "data" you are currently processing, rather than selecting all the "s" elements in the whole document.
Why not do this using apply-templates?
<xsl:template match="data">
...
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="s[../#m='R262']">
...
</xsl:template>
<xsl:template match="s[../#m='R263']">
...
</xsl:template>
If you want to use match template and apply-templates you could do the following which gives you also a text output just like your stylesheet does. So this XSLT applied to your original source XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:value-of select="#m"/>
<xsl:apply-templates select="s"/>
</xsl:template>
<xsl:template match="s">
Column1=<xsl:value-of select="#ut"/>
Column2=<xsl:value-of select="#lt"/>
</xsl:template>
</xsl:stylesheet>
gives you this output:
<?xml version="1.0" encoding="UTF-8"?>
R263
Column1=263firstrecord
Column2=2013-02-16T09:21:40.393
Column1=263secondrecord
Column2=2013-02-16T09:21:40.393
R262
Column1=262firstrecord
Column2=2013-02-16T09:21:40.393
Column1=262secondrecord
Column2=2013-02-16T09:21:40.393
You basically only match on the s and give out the attributes "ut" and "lt". You can also output XML which would look better.
Using 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>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="data">
<list>
<xsl:apply-templates select="s"/>
</list>
</xsl:template>
<xsl:template match="s">
<xsl:element name="record">
<xsl:attribute name="m">
<xsl:value-of select="parent::data/#m"/>
</xsl:attribute>
<item>Column1=<xsl:value-of select="#ut"/></item>
<item>Column2=<xsl:value-of select="#lt"/></item>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
will give you this nice XML output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<list>
<record m="R263">
<item>Column1=263firstrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
<record m="R263">
<item>Column1=263secondrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
</list>
<list>
<record m="R262">
<item>Column1=262firstrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
<record m="R262">
<item>Column1=262secondrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
</list>
You have to adapt the original XSLT a little bit to get a nice XML structure. Also when matching s you "climb" up to element data to get the R-numbers for your attribute values.
The template matching root you need for a proper XML root element. <list> you could also get rid off then you have <record> as child of <root>.

comparing nodes in xml with xslt

I am transforming xml data to html page wiith the help of xslt . I want to eliminate duplicate data where appears like this in the following way .
xml data
<calendar>
<event>
<date>May 11</date>
<description>Mother's Day</description>
</event>
<event>
<date>May 12</date>
<description>Birthday</description>
</event>
<event>
<date>May 12</date>
<description>Board Meeting</description>
</event>
</calendar>
My xslt code
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<h2>Event Dates </h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>date</th>
<th>description</th>
</tr>
<xsl:for-each select="calendar/event">
<tr>
<td><xsl:value-of select="date"/></td>
<td><xsl:value-of select="description"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
My output
date description
May 11 Mother's Day
May 12 Birthday
May 12 Board Meeting
Desired Output.
date description
May 11
Mother's Day
May 12
Birthday
Board Meeting
Please suggest me the XSLT code to modify .
Thanks in advance .
I found this solution and applied to your problem.
Jenni Tennison wrote a nice and short explanation of the method.
<?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:output method="text" indent="yes"/>
<xsl:key name="distinct-date" match="/calendar/event/date" use="./text()"/>
<xsl:template match="calendar">
<xsl:text>date description
</xsl:text>
<xsl:for-each select="event/date[generate-id(.) = generate-id(key('distinct-date',.)[1])]">
<xsl:value-of select="./text()"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="//event[date/text() = current()/text()]"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
<xsl:template match="event">
<xsl:text> </xsl:text><xsl:value-of select="description/text()"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
This short transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kDateByVal" match="date" use="."/>
<xsl:template match="/">
<xsl:text>date description</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match=
"date[generate-id()=generate-id(key('kDateByVal',.)[1])]">
<xsl:value-of select="concat('
',.)"/>
<xsl:for-each select="key('kDateByVal',.)">
<xsl:value-of select="concat('
',' ', ../description)"/>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
uses the classic Muenchian grouping method to transform the provided XML document:
<calendar>
<event>
<date>May 11</date>
<description>Mother's Day</description>
</event>
<event>
<date>May 12</date>
<description>Birthday</description>
</event>
<event>
<date>May 12</date>
<description>Board Meeting</description>
</event>
</calendar>
into the wanted, correct result:
date description
May 11
Mother's Day
May 12
Birthday
Board Meeting
The only way to solve your problem is a so called "Muenchian Grouping". Please refer to
Muenchian Grouping - group within a node, not within the entire document which is pretty much the same as your question, only with names instead of days.

xsl:sort with apply-templates not sorting

I have quite a large XSL document for an assignment that does a number of things. It is nearly complete but I missed a requirement that it has to be sorted and I cannot get it working. Here is a SSCCE of what is happening.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Root Document -->
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="staff">
<xsl:sort select="member/last_name" />
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="member">
<xsl:value-of select="first_name" /> <xsl:value-of select="last_name" /> <br/>
</xsl:template>
</xsl:stylesheet>
The XML file looks like this
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="sort.xsl"?>
<staff>
<member>
<first_name>Joe</first_name>
<last_name>Blogs</last_name>
</member>
<member>
<first_name>John</first_name>
<last_name>Smith</last_name>
</member>
<member>
<first_name>Steven</first_name>
<last_name>Adams</last_name>
</member>
</staff>
I was expecting the staff members to be listed by last name but they are not getting sorted. Please bear in mind that I am very inexperienced at XSLT.
<xsl:apply-templates select="staff">
<xsl:sort select="member/last_name" />
</xsl:apply-templates>
selects the staff elements and sorts them, but there is only one staff element, so this is a no-op.
Change to
<xsl:apply-templates select="staff/member">
<xsl:sort select="last_name" />
</xsl:apply-templates>
then that selects all the member elements and sorts them.
what is missing is a staff matching template or change the matching template to member like in this one:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Root Document -->
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="staff/member">
<xsl:sort select="last_name" />
</xsl:apply-templates>
</body>
</html>
</xsl:template>
<xsl:template match="member">
<xsl:value-of select="first_name" /> <xsl:value-of select="last_name" /> <br/>
</xsl:template>
</xsl:stylesheet>

xsl:element apply attributes

this question relates to
XSLT to generate html tags specified in XML
where I had an xml docment and used an xsl to generate html tags using
<xsl:element name="{Type}" >
the problem I have is that I want to specify some html attributes in my xml for example
<Page>
<ID>Site</ID>
<Object>
<ID>PostCode</ID>
<Type>div</Type>
<Attributes>
<Attribute name="class">TestStyle</Attribute>
<Attribute name="id">TestDiv</Attribute>
</Attributes>
<Class>display-label</Class>
<Value>PostCode</Value>
</Object>
</Page>
So does any one know how I could populate the xsl:element with the two attributes using xsl?
Thanks
Building from the stylesheet that I posted in the previous question, within the element declaration you can iterate through each of the Attributes/Attribute elements and construct the attributes for the element that you are constructing.
You are "standing" on the Object element node inside that for-loop, so you can then iterate over it's Attributes/Attribute elements like this:
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
Applied to your stylesheet:
<?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:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:for-each select="Page/Object">
<xsl:element name="{Type}" >
<xsl:for-each select="Attributes/Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="current()"/></xsl:attribute>
</xsl:for-each>
<xsl:value-of select="Value"/>
</xsl:element>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This is an alternative way to achieve the same output, but in a little more delcarative fashion, using apply-templates instead of for-each.
<?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:output method="html" indent="yes"/>
<xsl:template match="/">
<html>
<head>
</head>
<body>
<xsl:apply-templates select="Page/Object" />
</body>
</html>
</xsl:template>
<xsl:template match="Object">
<xsl:element name="{Type}" >
<xsl:apply-templates select="Attributes/Attribute" />
<xsl:apply-templates select="Value" />
</xsl:element>
</xsl:template>
<xsl:template match="Attribute">
<xsl:attribute name="{#name}"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
</xsl:stylesheet>
You need to fix the Attributes element in your source sample, it's not closed.
You can use xsl:for-each or xsl:apply-templates with select="Attributes/Attribute", to invoke an xsl:attribute element that looks a bit like this:
<xsl:attribute name="{#name}"><xsl:value-of select="text()"/></xsl:attribute>
The thing you need to watch out for is that xsl:attribute must come before anything that adds children to the {Type} element.
<xsl:element name="Attribute">
<xsl:attribute name="class">TestStyle</xsl:attribute>
<xsl:attribute name="id">TestDiv</xsl:attribute>
</xsl:element>