Classic ASP xsl date conversion - xslt

I have the following code that reads a rss feed into my page, but I would like to have the pubDate convert into a more human readable date if alt all possible.
<?xml version="1.0" encoding="iso-8859-1"?><!-- DWXMLSource="mm_news.xml" -->
<!DOCTYPE xsl:stylesheet]>
<xsl:output method="html" encoding="iso-8859-1"/>
<xsl:template match="/">
<p class="newsList-date"><xsl:value-of select="pubDate"/></p>........
This gives me:
Fri, 9 Sept 2011 15:21:36 GMT
but Would Like to read something like
Friday 9 Sept 2011
Even be happy if I could simply trim off the end to just have 'Fri, 9 Sept 2011'
Also if easier can I add an extra section within the xml so i can simply enter the date like I want it so I can read it, something like below? (The xml is hand written not dynamically created)
<?xml version="1.0" encoding="US-ASCII" ?>
<?xml-stylesheet title="XSL_formatting" type="text/xsl" href="direct.xsl"?>
<rss version="2.0">
<channel>....
<item>....
<title>.....
<description>....
<thedate>.....
Many Thanks

Well, the quick & dirty way would be to substitute select="pubDate" for an expression like this:
select="substring(pubDate,1,16)"
That one's dependent on the month being four letters however, and only gives you your 'fallback' result of 'Fri, 9 Sept 2011'.
If necessary, you can be a bit cleverer and remove the requirement of the month being four letters (which seems unlikely for May), by using this expression:
select="substring(pubDate,1,string-length(substring-before(pubDate,':'))-3)"
Rather than taking a fixed length of 16, it bases it on where the first : is (in the time), and subtracts 3 from that.
If you REALLY want, there's a one-line expression that can give you what you want, but it's a bit convoluted:
select="concat(normalize-space(substring('Monday Tuesday WednesdayThursday Friday Saturday Sunday ',string-length(substring-before('MonTueWedThuFriSatSun',substring(pubDate,1,3))) * 3 + 1,9)),substring(pubDate,5,string-length(substring-before(pubDate,':'))-7))"
This uses a 'lookup', to find where the day of the week exists in one string, and uses that to pick the full name from another, finally using 'normalize-space' to trim any extra spaces. Then it just concatenates it with the date part.

Related

Issue with xslt processing

I get the following error after my xslt has been processed:
There are 1 schema validation error(s):
1. Error Msg:The element 'BusinessObjectList' has incomplete content. List of possible elements expected: 'BusinessObject'. Line Number: 1, Line Position: 40, Severity:Error
I am trying to troubleshoot this issue and just require some clarifications. From my understanding of this error, there is a missing element called BusinessObject. So, I am not too sure if i have to incorporate this missing element or replace the existing element with this? another question too is, how to refer to Line 1, Line Position 40 in my xslt file?
Below is how the beginning of the xslt looks like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:template match="/">
<BusinessObjectList SchemaVersion="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="HierarchicalObjects-1.0.xsd">
You haven't told us how you are running the transformation, but it seems to be configured so that on completion the result document is validated against the schema at HierarchicalObjects-1.0.xsd. Presumably that schema says that the BusinessObjectList must contain at least N BusinessObject elements (perhaps N is 1, we don't know), and your transformation output includes less than N.
You either need to produce output that conforms to the schema, or you need to change the way you are processing to avoid the validation step.
The line/column number probably refers to a line/column in the result document, not in the stylesheet.

misleading line number in error message: XTSE0120: No character data is allowed between top-level elements

this is a self-answered post, since it took me some time to find the reason for the following XTSE0120.
I'm quite new to XSLT and made a mistake and put some text outside <xsl:template>:
bellack#bellack-TP-T430u:~$ cat -n /tmp/x.xsl
1 <?xml version="1.0" encoding="UTF-8" ?>
2 <xsl:stylesheet
3 version="2.0"
4 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
5 >
6 <xsl:output method="text"/>
7 <xsl:template name="build" match="/">
8 <!--
9 a lot of xslt code lines
10 -->
11 </xsl:template>
12 ****** misplaced text ****************
13 </xsl:stylesheet>
As this is not allowed, I got error XTSE120, see http://www.w3.org/TR/xslt-30/#err-XTSE0120:
bellack#bellack-TP-T430u:~/workspace/tembanking/generator/crud$ java -jar ../lib/saxon9.jar -xsl:/tmp/x.xsl -s:/dev/null
Error at xsl:template on line 7 of file:/tmp/x.xsl:
XTSE0120: No character data is allowed between top-level elements
Failed to compile stylesheet. 1 error detected.
But the Error is reported for line 7, and since x.xsl had some hundred lines, I was searching for something wrong around line 7 in vain, where the error was in fact at the end of the file.
I don't know if other XSLT processors than saxon also give this misleading error message.
Hope that helps somebody ...
Udo
Saxon only keeps line number information for element nodes, not for text nodes, so we have difficulty reporting an accurate position for the rare case where the error is in a text node.

Is there a DRY way to convert values prior to the transformation?

Lets say I have the following xml.
<root>
<data>
<a>ATTITUDE_ANNOYED</a>
<b>ATTITUDE_CAUTIOUS</b>
<c>25</c>
<d>30</d>
</data>
</root>
Ignoring the schema of my output, I want my output to present A as "Cautious" (one level up from annoyed), B as "
Pleased" (one level up from Cautious") and I want to perform some maths on C and D to convert the value into something slightly different.
I've had a look at a bunch of similar questions here (and I'm new to XSLT so maybe I don't quite get it) but a lot of the solutions appear to be "in-line", i.e. you modify the result as you transform it. This is okay but in my real example there are a lot of these values and I don't want to be performing the exact same conversion in multiple places (DRY). I just want to more or less pre-process the entire document and convert a bunch of values into other values (using just a few formulas) before I start the transformation.
What would be the best way to achieve this? I'm not particularly interested in performance so is there a way that I can run a prior transformation to easily transform specific values without modifying the structure?
UPDATE: (DevNull asked for my output desires) The output isn't exactly finalised. I'm trying to help a group at CivFanatics produce a guide on the differences between the AIs whose values derive from an xml file. There are a ton of leaders and a ton of values that need converting, by hand its taking them 2 hours per leader at the moment and the final formatting hasn't been decided on so I thought we'd all save time by using something like XLST.
Here is a rough example of a demo I'm working on.
<xsl:template match="data">
<h3>Attitude Thresholds</h3>
<table border="1">
<tr><td>Will open borders</td><td><xsl:value-of select="a"/></td></tr>
<tr><td>Will trade techs</td><td><xsl:value-of select="b"/></td></tr>
</table>
</xsl:template>
To clarify A and B are OpenBordersRefuseAttitudeThreshold and TechRefuseAttitudeThreshold. The guide is more readable if these are WillOpenBordersAt and WillTradeTechAt as opposed to the original values in the xml file so I need to nudge them up a value for the final output.
There are additional conversions that have been discussed:
iWonderConstructRand -> Builds Wonders
0 -> 0/10
5 -> 1/10
10 -> 2/10
15 -> 3/10
20 -> 4/10
25 -> 5/10
30 -> 6/10
35 -> 7/10
40 -> 8/10
45 -> 9/10
50 -> 10/10
So that's values such as C or D. They range between 0-50 and should be put into a 0-10 format for readability. There are a fair few of these values too.
Also there are conversions like "GoodieBaddie" which vary from 0 to 10. Which we wish to convert to something like:
0-3 -> Bad(x)
4-6 -> Neutral(x)
7-10 -> Good(x)
Where (x) is the original value.
Am I even using the right tool for the job here or is it borderline? I figured XLST would be a good choice to enable other contributors to not have to rely on the devs to make changes to the formatting/layout (as xlst is easier to edit then say C# or Python).
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:attitudes>
<a val="1">ANNOYED</a>
<a val="2">CAUTIOUS</a>
<a val="3">PLEASED</a>
</my:attitudes>
<xsl:variable name="vAttitudes" select="document('')/*/my:attitudes/*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(., 'ATTITUDE_')]/text()">
<xsl:variable name="vVal" select=
"$vAttitudes[. = substring-after(current(), '_')]/#val"/>
<xsl:value-of select="concat('ATTITUDE_', $vAttitudes[#val = $vVal+1])"/>
</xsl:template>
<xsl:template match="*[floor(.) = .]/text()">
<xsl:value-of select="concat(round(. div 5), '/10')"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<data>
<a>ATTITUDE_ANNOYED</a>
<b>ATTITUDE_CAUTIOUS</b>
<c>25</c>
<d>30</d>
</data>
</root>
produces the wanted, correct result:
<root>
<data>
<a>ATTITUDE_CAUTIOUS</a>
<b>ATTITUDE_PLEASED</b>
<c>5/10</c>
<d>6/10</d>
</data>
</root>

Counting XML based on date conditionals with xslt

I'm working with xslt for the first time. I have 2.0, but that's about the only advantage I have access to with the c# transform library we have. I'm trying to count a number of child nodes in the XML document which contain a date more than 12 years ago and have a certain type attribute.
Sample xml structure:
<xml version=\"1.0\" encoding=\"utf-8\"?>
<... />
<Dependents>
<Dependent><DOB>1964-04-01</DOB><DependentType>Spouse</DependentType></Dependent>
<Dependent><DOB>2000-01-01</DOB><DependentType>Child</DependentType></Dependent>
<Dependent><DOB>2012-01-01</DOB><DependentType>Child</DependentType></Dependent>
</Dependents>
<... />
where <... /> signifies some extra unrelated stuff.
So essentially, I want the number of children younger than 12 years old. (I have the count of dependenttype = child of all ages working, it's just the under 12 that's giving me trouble). The approach that was suggested to me was to construct a variable that stands for 12 years ago from today and use that as a basis for comparison in the count() function. That sounds reasonable, but I'm stuck constructing the date without the use of 3rd party libraries (e.g. exslt) that are so often linked in questions like this for the easy, handy-dandy answers.
The xslt I've gotten so far for this is:
<xsl:variable name="today" select="current-dateTime()" as="xs:dateTime" />
<xsl:variable name="twelveyearsago" select="xs:dateTime(concat(year-from-dateTime($today) - 12, '-', month-from-dateTime($today), '-', day-from-dateTime($today)))" />
<xsl:text>12yearsago=</xsl:text><xsl:value-of select="$twelveyearsago" />
And it's not working because the month-from-dateTime (and presumable day-from-dateTime) don't add leading zeros. For today, March 21 2012 I'm getting back: Saxon.Api.DynamicError : Invalid dateTime value "2000-3-21" (Month must be two digits) (The W3Schools xpath function reference implies that they should, but they don't.)
What I would like to output is:
<xsl:text>&numberofchildren=</xsl:text><xsl:value-of select="count(//InsuranceRequest/HealthInsurance/Dependents/Dependent/DependentType[text() = 'Child'])" />
<xsl:text>&childrenunder12=</xsl:text><xsl:value-of select="children under twelve" />
The more I pound my head against this, the more I feel like there's a simpler approach that I'm just not seeing.
Edit: I cleaned up the xslt syntax so it's valid and not a doubly-quoted c# string.
You can simply substract a duration of 12 years as in <xsl:variable name="twelveyearsago" select="$today - xs:yearMonthDuration('P12Y')"/>, then use e.g. //Dependent[DependentType = 'Child' and xs:date(DOB) >= $twelveyearsago].
[edit]
Here is a template that compiles and executes fine for with Saxon 9.4:
<xsl:template match="/">
<xsl:variable name="today" select="current-date()"/>
<xsl:variable name="twelve-years-ago" select="$today - xs:yearMonthDuration('P12Y')"/>
<xsl:value-of select="count(//Dependent[DependentType = 'Child' and xs:date(DOB) >= $twelve-years-ago])"/>
</xsl:template>

date minus another date in xslt

Hope someone can help. I am trying to compare 2 dates inside an XML file and using a XSLT to do some calculation:
For example I have 2 dates in XML: 2011-05-23 and 2011-04-29. I want to do calculation inside XSLT like below:
('2011-05-23'-'2011-04-29')*30 = 24*30= 720
Can anyone shed any light?
An XSLT 2.0 solution
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:value-of select="days-from-duration(
xs:date('2011-05-23')
- xs:date(xs:date('2011-04-29'))
)*30"/>
</xsl:template>
</xsl:stylesheet>
Yields: 720
xs:date() function evaluates the dates, which can be used to perform date operations
subtracting the second date from the first yields the xdt:dayTimeDuration P24D (24 days)
days-from-duration() extracts the days component from the xdt:dayTimeDuration (24)
then you can use that number to perform normal arethmatic (e.g. 24*30=720)
It might be worth looking at the EXSLT - date:difference solution. I believe that should do what you want, and there's even a straight XSLT implementation available.
Be aware though that the returned value is in duration format as specified in the XML Schema Part 2: Datatypes Second Edition, so it's likely you will need to process the result to derive the unit you wish to use in calculation (for instance in your example above you're expecting a result detailing the number of days difference - so you would need to pull out the relevant unit you want to work with, the result from date:difference in that case would most likely be "P24D").
Here's two templates I sometimes use for date calculations:
<xsl:template name="calcseconds">
<xsl:param name="date" />
<xsl:value-of select="(((substring($date,1,4) - 1970) * 365)+floor((substring($date,1,4) - 1970) div 4)+substring('000,031,059,090,120,151,181,212,243,273,304,334,365',substring($date,6,2)*4-3,3)+(substring($date,9,2)-1)+(1-floor(((substring($date,1,4) mod 4) + 2) div 3))*floor((substring($date,6,2)+17) div 20))*86400+(substring($date,12,2)*3600)+(substring($date,15,2)*60)+substring($date,18,2)" />
</xsl:template>
<xsl:template name="calcdays">
<xsl:param name="date" />
<xsl:value-of select="(((substring($date,1,4) - 1970) * 365)+floor((substring($date,1,4) - 1970) div 4)+substring('000,031,059,090,120,151,181,212,243,273,304,334,365',substring($date,6,2)*4-3,3)+(substring($date,9,2)-1)+(1-floor(((substring($date,1,4) mod 4) + 2) div 3))*floor((substring($date,6,2)+17) div 20))" />
</xsl:template>
They're a bit of a mouthful, but they'll calculate the number of seconds/days since midnight 1st January 1970 as an integer, which you can then do straight arithmetic on. They rely on the date format being yyyy-mm-dd hh:mm:ss, but manipulation of the parameters of the substring calls should allow you to process dates in any format you need.