tag:blogger.com,1999:blog-17553720411288102492024-02-21T12:04:07.516+00:00XSLT by Exampleby Miguel de MeloMiguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-1755372041128810249.post-26159048808693391092011-06-10T11:57:00.005+01:002011-06-10T12:15:59.326+01:00Using collection() with saxonOften you need to get a load of xml documents in scope of your transformation, a bit like an XML Database.<br /><br />Of course you can load each document to one or more variables, either at run-time or at the begining of the transformation. Alternatively you might want to consider the use of collection() to load documents from a local base URI, and voila all documents get parsed and are stored as a document() sequence in the scope of a variable.<br /><br />Saxon also allows you to define a particular file extention to filter the files in that collection, and activate a recursiev behaviour.<br /><br /><pre><br /><!-- uri of folder of input document --><br /><xsl:variable name="baseUri" select="replace(document-uri(/), '(.*)/.*$', '$1')"/><br /> <br /><!-- all schema documents in the base URI --><br /><xsl:variable name="schemas" select="collection(concat($baseUri, '?select=*.xsd;recurse=yes'))/xs:schema"/><br /></pre><br /><br />the code above resolves the input document URI, and then loads all *.xsd files recursively from the input document's hosting folder.<br /><br />The variable schemas now holds all *.xsd files as a sequence of xs:schema elements, so you can easily process the fileset just like the input document, or any other document loaded with the doc() function.<br /><br />Unfortunately, it looks like it's not possible to use xsl:key with documents loaded externally, as this would certainly speedup parts of the transform, and could easily represent a low-key equivalent to indexes in an XML Database.Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com1tag:blogger.com,1999:blog-1755372041128810249.post-31409736164339452192010-05-06T16:40:00.005+01:002010-05-06T16:46:48.566+01:00Obtain position() from “for” expression in Xpath 2One misunderstood and under utilized feature of XPath 2 is the for expression. The principal is fairly simple, whenever you have a sequence to iterate through, the “for” expression provides a simple mechanism to extract each of the sequences’ item() to a context variable and return something each time it changes context.<br /><br />For those familiar with XQuery FLOWR expression, the “for” expression is very similar, but offers less features, as “let”, “order by” and “where” aren’t included in XPath 2. <br /><br />One other feature of XQuery’s FLOWR expression, which is also missing from XPath 2, and I am providing a solution for in this blog is the “position” variable which can be useful at times, when you need to ad conditional statements to your return value, depending on the position of the item() in context.<br /><br />Let’s first have a look at how you’d use a “for” expression in XSL.<br /><pre><br /><xsl:value-of select="for $i in (1 to 9) return $i"/><br /></pre><br />returns: 1 2 3 4 5 6 7 8 9<br /><br />Lets now use a pre-defined sequence of xs:string items instead, stored in a variable, and comma separate the items, but for the 2nd item where we also want to append the character “*”. We also want to separate the penultimate and the last item with an “and”, rather than a comma. <br /><br />Because the “for” expression doesn’t provide any mechanism to keep track of context position, there isn’t a straight forward way to achieve the logic described above within the expression itself. Instead we’ll iterate the item count range, by creating a dynamic sequence based on the sequences’ item count.<br /><br /><pre><br /><xsl:variable name="family" select="('Miguel','Louise','Merlyn','Kai','Eden')" as="xs:string*"/><br /><br /><xsl:value-of select="<br /> for $i in (1 to count($family)) return <br /> concat(<br /> $family[$i], <br /> if ($i = 2) then '*' else (),<br /> if ($i = ( count($family) - 1)) then ' and' <br /> else if ($i < (count($family) - 1)) then ',' <br /> else ''<br /> )<br />"/> <br /></pre><br />Returns: Miguel, Louise*, Merlyn, Kai and Eden<br /><br />Traditionally a “for” expression in XPath 2 would directly iterate through the sequence you’re planning to extract values from, but in this example, you’ll notice the “for” expression is iterating through a number sequence, consisting of the numbers 1 to the item count of the sequence held by the vatiable $family. ((1 to count($family)))<br /><br />It is then possible to access each item of the $family variable, by dynamically providing the position of the item in the $family sequence, based on context. ($family[$i]).<br /><br />You can then add various conditional statements to your return value, based on the value of $I which holds the context position, rather than the value you’re planning to output. ( if ($i = 2) then '*' else ())<br /><br />The above example could be also achieved with an xsl:for-each which provides access to the position() function based on context.<br /><pre><br /><xsl:for-each select="$family"><br /> <xsl:value-of select="."/><br /> <xsl:value-of select="<br /> if (position() = (last() - 1)) then ' and '<br /> else if(not(position() = last())) then ', ' <br /> else ''<br /> "/><br /></xsl:for-each><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com8tag:blogger.com,1999:blog-1755372041128810249.post-11713023858952627822009-03-02T18:47:00.018+00:002009-03-21T20:03:37.946+00:00Adjusting time zone, and xs:dateTime formating with XSLT 2We're often asked to display and format dates in various formats using XSLT. Most of the time just formatting really, but less frequently we're asked to make time calculations, like for instance given a UTC (Coordinated Universal Time) time stamp, calculate the EST (US Eastern Standard Time) for that universal time stamp if the output is being rendered for a client within that time zone.<br /><br />Other more complex operations might include adding or subtracting years, months, days, hours, minutes, seconds or even a time zone offset to a given time stamp. Surely that sounds fairly straight forward at first, but if you start thinking about when a month might end in 30, 31 or 29, you can start picturing the endless combinations you might have to account for if you were to write standard XSL 1.0 syntax to achieve this kind of calculations. If you are using XSLT 1.0 I would probably suggest to resolve date manipulation before the xml reaches the processor, as the support for this kind of algorithm is very poorly supported.<br /><br />Let's say we have a universal time stamp for the 1st January 2009 at 3:00 am GMT, if you were to calculate the equivalent of that time stamp in EST (US Eastern Standard Time) you should output 31st December 2008 at 22:00 pm EST.<br /><br />The code examples below will output the current system's UTC (Coordinated Universal Time) time in EST (US Eastern Standard Time).<br /><span style="font-weight: bold;"><br />Saxon 9.3/9.5</span><br />For these processors, all that's required is to adjust the time stamp to the EST (US Eastern Standard Time) time zone, by invoking a time duration of minus 5 hours. The adjust-dateTime-to-timezone() function will kindly then return an xs:dateTime instance of the same time stamp in EST (US Eastern Standard Time). Note that Saxon does the automatic conversion of the time stamp from a #TEXT node value in an element, to xs:dateTime data type, so you don't actually need to invoke xs:dateTime manually.<br /><br />You then use adjust-dateTime-to-timezone() to move the date across the time zone spectrum. For this you'll need an xs:dayTimeDuration() to represent the amount of time corresponding to the time zone offset you are trying to adjust your time stamp to. In this case the offset is -5 hours from UTC/GMT, represented by -PT5H. xs:dayTimeDuration() not only performs the required calculations to subtract -5 hours from your time stamp, it will also add the relevant information to the time stamp signature to define it as in the appropriate time zone. For more on this function, have a look here <a href="http://www.w3.org/TR/xpath-functions/#func-adjust-date-to-timezone">http://www.w3.org/TR/xpath-functions/#func-adjust-date-to-timezone</a>.<br /><pre><br />fn:adjust-date-to-timezone($arg as xs:date?) as xs:date?<br /><br />fn:adjust-date-to-timezone(<br />$arg as xs:date?,<br />$timezone as xs:dayTimeDuration?<br />) as xs:date?<br /></pre><br />It is possible then to format the xs:dateTime to a format that suits your needs using format-dateTime(). This quite a comprehensive function, that uses a set of literal substrings known as "picture string" to define the display shape of your date time output. for example "[D1o] [MNn], [Y]" will produce "31st December, 2002".<br /><br />One particularly difficult date formating detail to obtain with other functions is the time zone acronym, most people just end up hard coding it like when in XSLT 1, but [ZN,*-3] will output that nicely for you.<br /><pre><br />format-dateTime( <br />$value as xs:dateTime?,<br />$picture as xs:string,<br />$language as xs:string?,<br />$calendar as xs:string?,<br />$country as xs:string?<br />) as xs:string?<br /></pre><br /><br />format-dateTime also offers another three parameters to help you further tune the output format to something more meaningful to the context of your output:<br />• Language - Specifies the language you want your date formatted as, for example en for english, fr for French and de for German. These are the ones I have tried successfully (see example below), although I am sure saxon supports many other languages.<br />• Calendar - Defines the type of calendar you need to use, for example ISO (ISO 8601 calendar), JE (Japanese Calendar), AH (Anno Hegirae "Muhammedan Era"), the default being AD (Anno Domini "Christian Era").<br />• Country - should give the location where the event in question took place, it's not the locale of the user of the information. (In fact, timezone formatting is the only thing Saxon uses it for; it was provided primarily for use with non-Gregorian calendars where the translation from Gregorian to another calendar may be location-dependent.). Time zone -5 never occurs in the UK, and we don't have a name for it; or rather, we call it EST if it happened in New York in winter, CDT if it happened in Chicago in summer, COT if it happened in Colombia, AST if it happened in Brazil, and so on. Hence the need to know where it happened.<br /><br />For further information on format-dateTime() have a look at <a href="http://www.w3.org/TR/xslt20/#function-format-dateTime">http://www.w3.org/TR/xslt20/#function-format-dateTime</a><br /><br />You'll need to declare the XML Schema namespace, that in this processor version will also make available a mechanism to invoke instances of data types such as dayTimeDuration.<br /><br /><span style="font-weight: bold;">XSL</span><br /><pre style="height:420px;"><br /><?xml version="1.0" encoding="UTF-8"?><br /><xsl:stylesheet<br /> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"<br /> xmlns:xs="http://www.w3.org/2001/XMLSchema"<br /> version="2.0"<br /><br /> exclude-result-prefixes="#all"<br /> ><br /> <xsl:output indent="yes"/><br /> <xsl:template match="/"><br /> <xsl:variable name="utc-timestamp" select="current-dateTime()"/><br /><br /> <xsl:variable name="gmt-timestamp" select="adjust-dateTime-to-timezone($utc-timestamp, xs:dayTimeDuration('PT0H'))"/><br /> <xsl:variable name="eu-timestamp" select="adjust-dateTime-to-timezone($utc-timestamp, xs:dayTimeDuration('PT1H'))"/><br /> <xsl:variable name="est-timestamp" select="adjust-dateTime-to-timezone($utc-timestamp, xs:dayTimeDuration('-PT5H'))"/><br /> <br /> <date timestamp="{$utc-timestamp}"><br /> <gmt str="{$gmt-timestamp}"><br /> <xsl:value-of select="format-dateTime($gmt-timestamp, '[D] [MN,*-3] [Y] [h]:[m01][PN,*-2] [ZN,*-3]', (), (), 'uk')"/><br /> </gmt><br /> <est str="{$est-timestamp}"><br /> <xsl:value-of select="format-dateTime($est-timestamp, '[D] [MNn] [Y] [h]:[m01][PN,*-2] [ZN,*-3]', (), (), 'us')"/><br /> </est><br /> <cet str="{$eu-timestamp}"><br /> <xsl:value-of select="format-dateTime($eu-timestamp, '[D] [MNn] [Y] [h]:[m01][PN,*-2] [ZN,*-3]', ('fr'), (), 'fr')"/><br /> </cet><br /> <cet str="{$eu-timestamp}"><br /> <xsl:value-of select="format-dateTime($eu-timestamp, '[Dwo] [MNn] [Y] [h]:[m01][PN,*-2] [ZN,*-3]', 'de', (), 'de')"/><br /> </cet><br /> <cet str="{$eu-timestamp}"><br /> <xsl:value-of select="format-dateTime($eu-timestamp, '[h].[m01][Pn] [ZN,*-3] [FNn], [D1o] [MNn] [Y]', ('sv'), (), 'sv')"/><br /> </cet><br /> </date><br /> </xsl:template><br /></xsl:stylesheet><br /></pre><br /><br /><br /><span style="font-weight: bold;">Output</span><br /><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><date timestamp="2009-03-02T17:55:13.47Z"><br /> <gmt str="2009-03-02T17:55:13.47Z">2 MAR 2009 5:55PM GMT</gmt><br /> <est str="2009-03-02T12:55:13.47-05:00">2 March 2009 12:55PM EST</est><br /> <cet str="2009-03-02T18:55:13.47+01:00">2 Mars 2009 6:55PM CET</cet><br /> <cet str="2009-03-02T18:55:13.47+01:00">zweite März 2009 6:55PM CET</cet><br /> <cet str="2009-03-02T18:55:13.47+01:00">6.55p.m. CET måndag, 2 mars 2009</cet><br /></date><br /></pre><br /><br /><span style="font-weight: bold;">saxon 8.7</span><br />If you're using Saxon 3.7, the processor will throw a stylesheet compilation error it you try to invoke an xs:dayTimeDuration() after declaring the XML schema namespace, so you'll need to declare the XPath data types namespace instead for exactly the same purpose. It seams to work well, but be careful with the name space URI, as I found a thread where Michael Kay is saying saxon has to often change the namespace URI for new releases to comply to W3C's latest URI's. Chances are that if you're using other versions of saxon, you might have to adjust your namespace URI for this to work.<br /><br /><pre><br />xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes"<br /><br /><xsl:variable name="est-time" select="adjust-dateTime-to-timezone(current-dateTime(), xdt:dayTimeDuration('-PT5H'))"/><br /><xsl:value-of select="format-dateTime($est-time, '[D] [MN,*-3] [Y] [h]:[m01][PN,*-2] [ZN,*-3]')"/><br /></pre><br /><br />If you're using an XSLT 1 processor and you're trying to implement similar behavior implemented it's awful and I wouldn't wish it upon my worst enemy. Have a look at the examples in the following link (http://geekswithblogs.net/workdog/archive/2007/02/08/105858.aspx), it just shows the amount of code required to get this functionality working using string manipulation, and simple arithmetic operations with standard XSLT 1 syntax.<br /><br />If your environment doesn't support an XSLT 2 processor, I would advise you to review the source of your XML and perhaps deal with the time conversion from the middle tire. I know it's easier said than done, but if you have access to standard APIs that already do the job properly, why reinvent the will . . . badly.<br /><br />One other final alternative would be to write yourself an XSLT extension in a language compatible with your processor, where you can define your own functions that will provide the desired functionality.Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com1tag:blogger.com,1999:blog-1755372041128810249.post-21388641368208652892008-02-18T11:56:00.001+00:002008-02-18T12:00:16.187+00:00Re-arrange the order of elements in an XML documentOften people need to reorganize their XML for further processing, and this is a great opportunity to clarify available techniques.<br /><br />You can use xsl:copy and/or copy-of, although I doubt your goal in practice will always be as simple as that.<br /><br /><br />Input xml:<br /><ROOT><br /><A1>A</A1><br /><B1>B</B1><br /><C1>C</C1><br /><D1>D</D1><br /></ROOT><br /><br />Required output xml: <br /><ROOT><br /><A1>A</A1><br /><D1>D</D1><br /><B1>B</B1><br /><C1>C</C1><br /></ROOT><br /><br /><br />If you need to restructure the children of the root element for example, and you're confident about the simplicity of the XML you're trying to restructure, then you'd do something like this:<br /><xsl:template match="/ROOT"><br /> <xsl:copy><br /> <xsl:copy-of select="A1"/><br /> <xsl:copy-of select="D1"/><br /> <xsl:copy-of select="B1"/><br /> <xsl:copy-of select="C1"/><br /> </xsl:copy><br /></xsl:template><br /><br />The above code will output what we've planned for. Now, if you have a much larger document, and what you're really after is moving some of the nodes around while maintaining an abstract hierarchy, you could use the technique in example code below.<br /><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><br /> <br /> <xsl:template match="*"><br /> <xsl:apply-templates select="self::*" mode="copy"/><br /> </xsl:template><br /> <br /> <xsl:template match="A1"><br /> <xsl:apply-templates select="self::*" mode="copy"/><br /> <xsl:apply-templates select="../D1" mode="copy"/><br /> </xsl:template><br /> <br /> <br /> <xsl:template match="B1"><br /> <xsl:apply-templates select="self::*" mode="copy"/><br /> <xsl:apply-templates select="../C1" mode="copy"/><br /> </xsl:template><br /> <br /> <xsl:template match="D1"/><br /> <xsl:template match="C1"/><br /> <br /> <xsl:template match="*" mode="copy"><br /> <xsl:copy><br /> <xsl:apply-templates/><br /> </xsl:copy><br /> </xsl:template><br /> <br /> <xsl:template match="text()"><br /> <xsl:value-of select="."/><br /> </xsl:template><br /></xsl:stylesheet><br /><br /><br />In this case you are copying an abstract document structure and repositioning some of the elements in strategic places.Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com3tag:blogger.com,1999:blog-1755372041128810249.post-34384569247058382002008-02-14T12:33:00.004+00:002009-03-21T12:57:16.550+00:00When to use xsl:if and xsl:whenIn the last few weeks I have been approached by work colleagues for advice on this subject a couple of times, so I guess there must be more people out there that will either be searching the web for answers or aren't entirely clear about when xsl:if or xsl:when should be used, and the constraints of using either.<br /><br /><br />I am sure most people understand that xsl:if represents a conditional statement, just like most languages do, in fact I can't thing of a language that doesn't. Even XPath 2 now offers conditional statements.<br /><br /><xsl:if test="/pub[@name='The Elephants Head']/@status = 'closed'">dammit, what do we do now?</xsl:if><br /><br />If The Elephants Head is closed, we have ourselves a good question as the text node output of this conditional statement, "dammit, what do we do now?".<br /><br />The difference in conditional statements between XSL and other languages is that elseif and else are represented in a different construct by xsl:when and xsl:otherwise, both children of xsl:choose.<br /><br /><xsl:choose><br /> <xsl:when test="/pub[@name='Elefants Head']/status = 'opened'">top banana, I'll have a pint of Krony please</xsl:when><br /> <xsl:otherwise>pants, we'll drink at The Mixer instead</xsl:otherwise><br /></xsl:choose><br /><br />There we go, in this case we have a far more structured conditional statement if The Elephants Head pub is opened "top banana, I'll have a pint of Krony please", although if it is closed closed, I know exactly what to do next, "pants, we'll drink at The Mixer instead".<br /><br />So lets say I am an indecisive guy, and am not quite sure what pub to go to, but have some solid perquisites to help my decision, alongside a psychic bit of information in the form of an XML document:<br /><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><pubs><br /> <pub name="The Elephants Head" matesInside="0" status="opened"/><br /> <pub name="The Devenshire Arms" matesInside="0" status="closed"/><br /> <pub name="The Worlds End" matesInside="2" status="opened"/><br /> <pub name="The Good Mixer" matesInside="3" status="opened"/><br /></pubs><br /></pre><br /><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"><br /> <xsl:template match="/pubs"> <br /> <xsl:choose><br /> <xsl:when test="pub[@name='The Elephants Head'][@status = 'opened'][@matesInside &gt; 0]">top banana, I'll have a pint of Krony please</xsl:when><br /> <xsl:when test="pub[@name='The Devenshire Arms'][@status='opened'][@matesInside &gt; 0]">head off to the Devensire, and see dead people</xsl:when><br /> <xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]">head off to the Worlds End, to check out the gigs</xsl:when><br /> <xsl:otherwise>pants, we'll drink at The Mixer instead, someone will turn up soon enough</xsl:otherwise><br /> </xsl:choose><br /> </xsl:template><br /></xsl:stylesheet><br /></pre><br /><br />So here I go walking down Camden High Street, as I approach The Elephants Head, I look inside and see the pub is opened and check if there are any of my mates inside, if this condition is true, mate, I'm on my way to the bar to get that cold pint of Krony.<br /><br />Now lets consider the XML document above, and as you can see Billy no mates me at the Elephants, there's no one in, dammit. In this case XSL has an hierarchical decision making engine built in, that helps prioritizing the options in ascending position order.<br /><br />First I check if the Devenshire Arms is opened, and weather I'm facing yet another Billy no mates scenario here. In this case as defined by the XML document the Dev is closed, so no one's there at all, bloody goths, probably sitting in a corner at home feeling sorry for themselves.<br /><br />Ok . . . whats' the next prospect here . . . we have The Worlds End, it's opened, and guess what, 3 of my mates are inside enjoying the beverage . . . hurrah, I guess "head off to the Worlds End, to check out the gigs" it's the option I'd go for in this instance.<br /><br />Obviously as oblivious as I was (bless), there were already about 3 other mates comfortably on the booze for some time at the Mixer, and failing all of the previous options eventually my fate would indeed be "pants, we'll drink at The Mixer instead, someone will turn up soon enough".<br /><br />So to recap xsl:if provides a mechanism to make a boolean evaluation of one or more xpath expressions, and xsl:choose, provides the same boolean evaluation mechanism hierarchically via a collection of xsl:when with the addition of a graceful fail over option.<br /><br />I have also been asked frequently about combining xsl:if and xsl:choose/xsl:when. Is this bad practice?, I am asked. The answer is:It depends on the circumstances, it's frequently required to have both in the same algorithm, to assure you deliver an accurate output. Often the combination of xsl:if and xsl:choose might improve performance.<br />For example I knew the code above would not need to be executed unless I knew at least one of the pubs was opened, so I could wrap the xsl:choose in an xsl:if<br /><br /><xsl:if test="pub[@status = 'opened']"><br /> <xsl:choose><br /> <xsl:when test="....<br /> </xsl:choose><br /></xsl:if><br /><br />This case would probably be past 3:00am or something, and I wouldn't even be considering any of those options. Saves processing power and clearly documents that unless at least one of the pus is opened, there isn't any reason to even consider any of those options.<br /><br />Sometimes is also useful to nest xsl:if inside an xsl:when, to further filter your options:<br /><pre><br /> <xsl:choose><br /> <xsl:when test="...<br /> <xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]"><br /> <xsl:if test="@timeToShut = 'last orders'">get to the bar as quickly as possible</xsl:if><br /> </xsl:when><br /> <xsl:otherwise...<br /> </xsl:choose><br /></pre><br />or even xsl:choose inside xsl:when<br /><pre><br /> <xsl:choose><br /> <xsl:when test="...<br /> <xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]"><br /> <xsl:choose><br /> <xsl:when test="pub/mate/pint[@status='nearely finished']"><br /> <xsl:text>order </xsl:text><br /> <xsl:value-of select="count(pub/mate/pint[@status='nearely finished']) + 1"/><br /> <xsl:text>pints.</xsl:text><br /> </xsl:when><br /> <xsl:otherwise>it's only one pint for me, thank you.</xsl:otherwise><br /> </xsl:choose><br /> </xsl:when><br /> <xsl:otherwise...<br /> </xsl:choose><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com7tag:blogger.com,1999:blog-1755372041128810249.post-62803918908820420482008-02-12T20:36:00.000+00:002008-02-12T21:53:56.788+00:00XPath expressions special characters in XSLThere is a common misunderstanding about the use of special characters by Xpath expressions in XSL and Xpath expressions in other languages, I'll try to help clarify some of the myths in this blog.<br /><br />If you were using Java or C#, the actual expression would be either passed as a string parameter to a constructor method or any other method, or even just stored in a string variable to then be used as a parameter to one of those methods. Simple. Although, if you'd consider the following example, you'd have to escape the double quote characters with a back slash character.<br /><br />String xPathExpr = "/root/my/node[@attribute='\"this & that value in quotes\"']";<br /><br />In XML you have similar constraints, if you try to put a double quote character inside the value of an attribute, the XML parser thinks the value of the attribute ends on the first occurrence of the double quote character, therefore the document won't be a valid XML document. Unless of coarse you replace the character with the equivalent character reference &#34;.<br /><br />Because XSL documents are XML documents, some of these rules need to be taken into account. By default most XSLT Procesors allocate entities to at least 3 of those character references:<br /><!ENTITY amp CDATA "&#38;" -- ampersand, U+0026 ISOnum --><br /><!ENTITY lt CDATA "&#60;" -- less-than sign, U+003C ISOnum --><br /><!ENTITY gt CDATA "&#62;" -- greater-than sign, U+003E ISOnum --><br /><br />Meaning you can use those three by preceding the entity name with an "&" character and terminate the entity refference with an ";" character.<br />&amp; = &<br />&lt; = <<br />&gt; = ><br /><br />So if you were to use the earlier XPath expression in XSL it'd look like this instead:<br /><br /><xsl:variable name="myNode" select="/root/my/node[@attribute='&#34;this &amp; that value in quotes&#34;'"/><br />or<br /><xsl:variable name="myNode" select="/root/my/node[@attribute='&#34;this &#38; that value in quotes&#34;'"/><br /><br />The characters > and < are often used in XPath expressions that evaluate numerical values.<br /><br /><xsl:variable name="isMyOtherAttributeGreater" select="/root/my/node/@attribute &gt; /root/my/other/node/@otherAttribute"/><br />This expression returns a Boolean value, of true when the value of @attribute of the first XPath expression is greater than value of the @otherAttribute in the second expression.<br /><br />You could also combine < and > with the "=" character to construct the greater than or equal and the less than or equal evaluation expressions.<br />@attribute &gt;= @otherAttribute<br />@attribute &lt;= @otherAttribute<br /><br /><xsl:variable name="myNode" select="/root/my/node[@attribute &gt;= ../node[1]/@otherAttribute]"/><br /><br /><br />see following links for further info on entities and character encoding in XML:<br />http://www.xml.com/pub/a/98/08/xmlqna0.html<br />http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references<br />http://www.w3.org/TR/html401/sgml/entities.htmlMiguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com7tag:blogger.com,1999:blog-1755372041128810249.post-4345430197581813912007-06-26T11:01:00.002+01:002009-03-21T13:03:59.609+00:00Sort by date greater than other dateOften XML contains dates in all sorts of formats, and either XSL Developers have to go through a great deal of diplomatic negotiations with Developers responsible for the XML, or worse there is no one to talk to. <br /><br />Ultimately the job needs to get done, and as XSL 1.0 at least does not recognise the Date data type natively, it’s back to hacking away the xsl:sort element so the sorting can take place.<br /><br />As XSL 1.0 Processors understand number sorting, dates can easily and meaningfully be sorted in the format yyyymmdd, which represents a meaningful numeric value for any given date, maintaining the correct order once sorted. This means that no matter what format you get dates in xml as soon as you manage to convert the date to yyyymmdd you can sort by Date.<br /><br />Lets say you have some date in the traditional US format mm-dd-yyyy (10-05-1971). To format this date you know that all the characters before the first occurrence of the hyphen character represent the month, so you can easily get those with substring-before([dateNode], ‘-‘). To get the day, you can use the same technique twice, but this time using substring-before and substring-after combined, substring-before(substring-after([dateNode], ‘-‘), ‘-‘) . <br /><br />To get year, you guessed . . . substring-after(substring-after([dateNode], ‘-‘), ‘-‘)<br /><br />This technique works for the following formats:<br />mm-dd-yyyy<br />dd-mm-yyyy<br />d-m-yyyy<br />d-m-yy<br /><br />If you knew the exact character length of your date you could use the substring() function instead:<br />Month – substring([dateNode], 1, 2)<br />Day – substring([dateNode], 4, 2)<br />Year – substring([dateNode], 7, 4)<br /><br />Whatever technique you use, all you need to do is to concatenate the string fragmentation results of your date string to yyyymmdd, or equivalent, like:<br />concat(<br />substring-after(substring-after([dateNode], ‘-‘), ‘-‘),<br />substring-before([dateNode],<br />substring-before(substring-after([dateNode], ‘-‘)<br />)<br /><br />You can now use the concatenated string inside an XPath predicate.<br /><br />The following example creates a list of news where the date is greater than the expiry date and sorts the results by date descending.<br /><br /><br />XML<br /><pre><br /><PICNews><br /> <news><br /> <Date>6/31/2007</Date><br /> <Expires>6/30/2007</Expires><br /> <Author>Dan Jurden</Author><br /> <Type>Memo</Type><br /> <Text>This is a memo.</Text><br /> </news><br /> <news><br /> <Date>7/21/2007</Date><br /> <Expires>6/30/2007</Expires><br /> <Author>Dan Jurden</Author><br /> <Type>News</Type><br /> <Text>This is some news.</Text><br /> </news><br /> <news><br /> <Date>6/22/2007</Date><br /> <Expires>6/23/2007</Expires><br /> <Author>Dan Jurden</Author><br /> <Type>Alert</Type><br /> <Text>This is an alert.</Text><br /> </news><br /></PICNews><br /></pre><br /><br /><br />XSL<br /><pre style="height:350px;"><br /><xsl:template match="/"><br /> <body><br /> <h2>PIC Information</h2><br /> <div id="news"><br /> <xsl:apply-templates select="<br /> PICNews/news[<br /> concat( <br /> substring(Date, (string-length(Date) - 3), string-length(Date)), <br /> substring-before(Date, '/'), <br /> substring-before(substring-after(Date, '/'), '/') <br /> )<br /> <br /> > <br /> <br /> concat( <br /> substring(Expires, (string-length(Expires) - 3), string-length(Expires)), <br /> substring-before(Expires, '/'),<br /> substring-before(substring-after(Expires, '/'), '/') <br /> )<br /> <br /> ]<br /> "><br /> <br /> <xsl:sort select="<br /> Date[ <br /> concat(<br /> substring(Date, (string-length(Date) - 3), string-length(Date)),<br /> substring-before(Date, '/'),<br /> substring-before(substring-after(Date, '/'), '/')<br /> )<br /> ]<br /> " <br /> order="descending"/> <br /> </xsl:apply-templates> <br /> </div><br /> </body><br /></xsl:template><br /><br /><br /><xsl:template match="news"><br /> <div class="newsItem"><br /> <img src="images/{translate(<br /> Type, <br /> 'QWERTYUIOPASDFGHJKLZXCVBNM',<br /> 'qwertyuiopasdfghjklzxcvbnm'<br /> )<br /> }.jpg" <br /> width="32px"/><br /> <p class="date"><br /> <xsl:text>Date: </xsl:text><br /> <xsl:value-of select="Date"/><br /> </p><br /> <p class="news-text"><br /> <xsl:value-of select="Text"/><br /> </p><br /> </div><br /></xsl:template><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com4tag:blogger.com,1999:blog-1755372041128810249.post-61523161813190649012007-06-26T09:57:00.000+01:002007-06-26T11:09:19.358+01:00Good use of xsl:keyThis is a perfect example of how xsl:key can not only optimize the syntax of your xsl, but also its performance significantly.<br />In the following XML there are three separate nodesets, children of the same parent, that reference each other via different ids. The purpose of this exercise is to get a list of incidents for each car. <br /><br />XML<br /><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><root><br /> <car><br /> <CarInfo vin="4FL200212345"><br /> <Owner>J. P. Morgan</Owner><br /> </CarInfo><br /> </car><br /> <car><br /> <CarInfo vin="3CL200212345"><br /> <Owner>J.P. Thompson</Owner><br /> </CarInfo><br /> </car><br /> <carSpecifics vin="4FL200212345"><br /> <CarMake xrefId="XREF12345"><br /> <Color>Red</Color><br /> <Style>Sedan</Style><br /> </CarMake><br /> </carSpecifics><br /> <carSpecifics vin="3CL200212345"><br /> <CarMake xrefId="XREF67890"><br /> <Color>Red</Color><br /> <Style>Sedan</Style><br /> </CarMake><br /> </carSpecifics><br /> <CarXref xrefId="XREF12345"><br /> <Incident>Friday Crash</Incident><br /> </CarXref><br /> <CarXref xrefId="XREF67890"><br /> <Incident>Sat Crash</Incident><br /> </CarXref><br /></root><br /></pre><br /><br />XSL<br /><pre><br /> <xsl:key name="incidents" <br />match="/root//CarXref/Incident" use="../@xrefId"/><br /> <xsl:key name="carSpecifics" <br />match="/root/carSpecifics/CarMake/@xrefId" use="../../@vin"/><br /> <br /> <xsl:template match="/"><br /> <incidents><br /> <xsl:apply-templates select="root/car/CarInfo/@vin"/><br /> </incidents> <br /> </xsl:template><br /> <br /> <xsl:template match="@vin"> <br /> <xsl:apply-templates select="key('carSpecifics',.)"/><br /> </xsl:template><br /> <br /> <xsl:template match="@xrefId"><br /> <xsl:apply-templates select="key('incidents', .)"/><br /> </xsl:template><br /> <br /> <xsl:template match="Incident"><br /> <incident><br /> <xsl:value-of select="."/><br /> </incident><br /> </xsl:template><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com2tag:blogger.com,1999:blog-1755372041128810249.post-76543629350139221932007-06-25T09:12:00.000+01:002007-06-25T09:18:45.648+01:00Copying nodes depending on children valuesThe input XML needs to be refined to contain only nodes where the value element of a characteristics element where the name attibute is weight is greater than 15.<br /><br />XML<br /><pre><br /><root><br /> <quality><br /> <batch id="1"><br /> <characteristic name="weight"><br /> <value>12</value><br /> </characteristic><br /> <characteristic name="length"><br /> <value>45</value><br /> </characteristic><br /> <characteristic name="volume"><br /> <value>67</value><br /> </characteristic><br /> </batch><br /> <batch id="2"><br /> <characteristic name="weight"><br /> <value>12</value><br /> </characteristic><br /> <characteristic name="weight"><br /> <value>17</value><br /> </characteristic><br /> </batch><br /> <batch id="3"><br /> <characteristic name="weight"><br /> <value>56</value><br /> </characteristic> <br /> <characteristic name="volume"><br /> <value>65</value><br /> </characteristic><br /> </batch><br /> </quality><br /></root> <br /></pre><br /><br />XSL<br /><pre><br /><xsl:transform <br /> xmlns:xsl="http://www.w3.org/1999/XSL/Transform" <br /> version="1.0"><br /> <xsl:template match="/root"><br /> <quality><br /> <xsl:copy-of select="<br />quality/batch[characteristic[@name= 'weight' and value &gt; 15]]<br /> "/><br /> </quality><br /> </xsl:template> <br /></xsl:transform><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com0tag:blogger.com,1999:blog-1755372041128810249.post-79525124215439693902007-06-24T23:49:00.000+01:002007-06-25T00:10:45.813+01:00Horizontally scalable HTML tableBuild an horizontally scalable HTML table using XPath and XSLT<br /><br /><br /><span style="font-weight: bold;">XML</span><br /><pre><br /><?xml version="1.0" encoding="UTF-8"?><br /><ProducsList><br /> <Product id="1"><br /> <SpecList><br /> <Spec><br /> <SpecLabel>Height</SpecLabel><br /> <SpecValue>10</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Width</SpecLabel><br /> <SpecValue>6</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Weight</SpecLabel><br /> <SpecValue>20.5</SpecValue><br /> <SpecCat>Weigth</SpecCat><br /> </Spec><br /> </SpecList><br /> </Product><br /> <Product id="2"><br /> <SpecList><br /> <Spec><br /> <SpecLabel>Height</SpecLabel><br /> <SpecValue>8</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Width</SpecLabel><br /> <SpecValue>5</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Weight</SpecLabel><br /> <SpecValue>18</SpecValue><br /> <SpecCat>Weigth</SpecCat><br /> </Spec><br /> </SpecList><br /> </Product><br /> <Product id="3"><br /> <SpecList><br /> <Spec><br /> <SpecLabel>Height</SpecLabel><br /> <SpecValue>5</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Width</SpecLabel><br /> <SpecValue>2</SpecValue><br /> <SpecCat>Dimension</SpecCat><br /> </Spec><br /> <Spec><br /> <SpecLabel>Weight</SpecLabel><br /> <SpecValue>10</SpecValue><br /> <SpecCat>Weigth</SpecCat><br /> </Spec><br /> </SpecList><br /> </Product><br /></ProducsList><br /></pre><br /><br /><br /><span style="font-weight: bold;">XSL</span><br /><pre><br /><xsl:template match="ProducsList"><br /> <table><br /> <tr><br /> <th>Label</th><br /> <xsl:apply-templates select="Product" mode="header"/><br /> </tr><br /> <xsl:apply-templates select="<br />/ProducsList/Product/SpecList/Spec/SpecLabel[ <br /> not( <br /> text() <br /> = <br /> ancestor::Product/preceding-sibling::Product//SpecLabel/text()<br /> ) <br /> ]<br /> "<br /> /> <br /> </table><br /></xsl:template><br /><br /><xsl:template match="Product" mode="header"><br /> <th><br /> <xsl:text>Product </xsl:text><br /> <xsl:value-of select="@id"/><br /> </th><br /></xsl:template><br /><br /><xsl:template match="SpecLabel"><br /> <xsl:variable name="text" select="text()"/><br /> <tr><br /> <th><br /> <xsl:value-of select="$text"/><br /> </th><br /> <xsl:apply-templates select="/ProducsList/Product"><br /> <xsl:with-param name="label" select="$text"/><br /> </xsl:apply-templates><br /> </tr><br /></xsl:template><br /><br /><xsl:template match="Product"><br /> <xsl:param name="label"/><br /> <td><br /> <xsl:value-of select="SpecList/Spec[SpecLabel=<br /> $label]/SpecValue"/><br /> </td><br /></xsl:template><br /></pre><br /><br /><br /><span style="font-weight: bold;">HTML</span><br /><pre><br /><table><br /> <tr><br /> <th>Label</th><br /> <th>Product 1</th><br /> <th>Product 2</th><br /> <th>Product 3</th><br /> </tr><br /> <tr><br /> <th>Height</th><br /> <td>10</td><br /> <td>8</td><br /> <td>5</td><br /> </tr><br /> <tr><br /> <th>Width</th><br /> <td>6</td><br /> <td>5</td><br /> <td>2</td><br /> </tr><br /> <tr><br /> <th>Weight</th><br /> <td>20.5</td><br /> <td>18</td><br /> <td>10</td><br /> </tr><br /></table><br /></pre>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com0tag:blogger.com,1999:blog-1755372041128810249.post-27153847868759783442007-06-22T15:01:00.000+01:002007-06-24T23:54:34.713+01:00top id from xml<a href="http://groups.google.com/group/microsoft.public.xsl/browse_thread/thread/7cce3d9d14272703/03bfdff09a7b963a?#03bfdff09a7b963a"></a><br />Clever little XPath 1.0, gets the node with the highest id value. Xpath 2.0 has the max() function that ultimately does the same thing.<br /><br />/rt/item[@id[not(. &lt; ../../item/@id)]]<br /><br /><rt><br /><item id="1"></item></rt><rt><br /><item id="1"></item><item id="1">txt<item id="1"></item></item><br /><item id="1"></item><item id="2">txt<item id="1"></item></item><br /><item id="1"></item><item id="5">txt<item id="1"></item></item><br /><item id="1"></item><item id="4">txt<item id="1"></item></item><br /></rt> <rt><item id="1"></item><item id="4"></item><br /></rt><br /><br />returns<br /><br /><item id="5">txt<item id="1"></item></item>Miguel de Melohttp://www.blogger.com/profile/17585549877555806457noreply@blogger.com0