Flexible XSL Date / DateTime Transformation - xslt

We're using Tibco BusinessWorks to pass an XML document to a Tibco BusinessEvents process. Because BusinessEvents does not have a DATE format, only DATETIME, we must change Dates in the source XML document before sending to BusinessEvents, and map the response's DateTime values back to simple Dates.
This is both annoying and cumbersome.
In an attempt to improve BusinessWorks' performance, I'm writing a stylesheet to handle the mapping. Here's what I've got.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:inf="http:/the.company.namespace">
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="inf:PriorExpirationDate | inf:OrderDate | inf:SegmentEffectiveDate |
inf:SegmentExpirationDate | inf:CancelDate | inf:NewBusinessEffectiveDate |
inf:NewBusinessExpirationDate | inf:RenewalEffectiveDate | inf:RenewalExpirationDate |
inf:QuestionDate | inf:ViolationDate | inf:ConvictionDate |
inf:EffectiveDate | inf:RatingDate | inf:AdvanceDate |
inf:SIDRevisionDate | inf:DriverLicensedDate |
inf:ESignatureDate | inf:UploadDate | inf:CancelDate |
inf:CancelProcessedDate | inf:CancelEffectiveDate | inf:CreatedDate |
inf:QuoteCreationDate | inf:QuoteModifiedDate | inf:QuoteExpirationDate |
inf:RateStartDate | inf:RateEndDate | inf:ChangeEffectiveDate | inf:PostDate |
inf:EffectiveDate | inf:ExpirationDate | inf:BirthDate |
inf:InstallmentDueDate | inf:CommercialDriverLicenseDate ">
<xsl:element name="{name()}">
<xsl:value-of
select="concat(format-date(text(),'[Y0001]-[M01]-[D01]'), 'T00:00:00')" />
</xsl:element>
</xsl:template>
While functional, this not ideal. I'd prefer NOT to have to enumerate each element I need transformed, I'd rather specify a TYPE that needs to be converted.
(1) Does XSL offer this functionality ?
(2) Alternatively, there are some element names that may be DATEs in one location and DATETIMEs in others. Is there an efficient way of excluding some DATETIME elements if I know the parent by name ?
(3) Lastly, does anyone see room for enhancement beyond the scope of the question ?
Some context: The original mapping is done inside BusinessWorks' editor, where the GUI generates its own mapping file, a several-hundred-line series of if/then/else statements. For a 50k document (our average) this amounts to nearly 20ms overhead per transformation for a web service that completes its actual work in fewer than 50ms. This is the bottleneck that must be improved upon.

Just use:
<xsl:template match="*[. castable as xs:date]">
<!-- Your code here -->
</xsl:template>

Related

how to get document that contain more than collection in xslt?

let $stylesheet := "abc.xsl"
let $params := map:map()
let $_ := map:put ($params,"col1","abc")
return
xdmp:xslt-invoke(
$stylesheet, (), $params,
<options xmlns="xdmp:eval">
<template>a:schema</template>
</options>)
abc.xsl
<xsl:template name="a:schema">
<xsl:param name="collection-uri" as="xs:string" select="$col1"/>
<xsl:apply-templates select="collection($collection-uri)"/>
</xsl:template>
In this currently ,we are taking all the document ,which is coming in collection "abc".
But I want to add more than one collection in $param map, so that the document which contain ,both collection "abc" and "def" will comes
for example :
| Document| collection
|:---------|:----------:|
| Doc1 | abc, def |
| Doc2 | abc |
| Doc3 | abc, def |
it will pick Doc1 and Doc3
collection() accepts a sequence of xs:string, but would return any of the documents in either of the collections specified.
If you want only the docs that are in all of the collections specified, you could use cts:search() with a sequence of cts:collection-query() inside of a cts:and-query().
<xsl:template match="/">
<xsl:param name="collection-uri" as="xs:string*" select="$col1"/>
<xsl:apply-templates select="cts:search(doc(), cts:and-query(( $collection-uri ! cts:collection-query(.) )))"/>
</xsl:template>
Enable the 1.0-ml dialect, so that you can use the cts built-in functions by adding the following attribute to your xsl:stylesheet element:
xdmp:dialect="1.0-ml"
The $collection-uri param is declared as xs:string, so it will only have one string value. You could change that to be a sequence of strings with either * or + quantifier:
<xsl:param name="collection-uri" as="xs:string*" select="$col1"/>
and then set the collections on the $col1 param:
let $_ := map:put ($params,"col1", ('abc', 'def'))

XSLT 2.0 / XPATH - choose-when testing node

In XPATH under XSLT 2.0, I am unclear as to why an xsl:choose/xsl:when #test isn't working.
When I run this template testing for the element tei:del[#rend='expunctus'], the test DOES NOT return the result:
<xsl:template match="tei:del[#rend='expunctus'] |
tei:gap |
tei:sic |
tei:supplied[#reason='added'] |
tei:surplus[#reason='repeated' or #reason='surplus'] |
tei:unclear">
<xsl:choose>
<xsl:when test="tei:del[#rend='expunctus']">
[<xsl:text>EXPUNCTUS</xsl:text>]
</xsl:when>
</xsl:template>
When I run this template with just the attribute #rend='expunctus' as the test, the test DOES return the result:
<xsl:template match="tei:del[#rend='expunctus'] |
tei:gap |
tei:sic |
tei:supplied[#reason='added'] |
tei:surplus[#reason='repeated' or #reason='surplus'] |
tei:unclear">
<xsl:choose>
<xsl:when test="#rend='expunctus'">
[<xsl:text>EXPUNCTUS</xsl:text>]
</xsl:when>
</xsl:template>
Is this because of the current node already selected?
I prefer to test against the element, not just the attribute, to eliminate possible ambiguity.
Thanks.
Yes, it is because of the current node selected.
Your template matches tei:del[#rend='expunctus'] (amongst other things), so when you do <xsl:when test="tei:del[#rend='expunctus']"> this is relative to the node you have matched, so it is looking for another tei:del as a child node of the current node.
What you probably need to do is this...
<xsl:when test="self::tei:del[#rend='expunctus']">
Alternatively, consider using separate templates for each possible node and putting any shared code in a named template.

XSLT converting style down from 3.0 to 2.0 - variable reference error

I have a large XSL 3.0 stylesheet which I need to convert to XSL 2.0. It seems there is one error repeated throughout the document, but I'm not sure how to fix it.
For example, in XSL 3.0 I declare these variables:
<xsl:variable name="glosspath"
select="/tei:TEI/tei:text//tei:add"/>
<xsl:variable name="apppath"
select="/tei:TEI/tei:text//tei:del | /tei:TEI/tei:text//tei:surplus | /tei:TEI/tei:text//tei:supplied[#reason='added']
| /tei:TEI/tei:text//tei:choice[#style='sic'] | /tei:TEI/tei:text//tei:sic[#style='sic'] | /tei:TEI/tei:text//tei:gap
| /tei:TEI/tei:text//tei:note[#type='reading']"/>
And then I use these variables in a variety of contexts such as
<xsl:template match="$glosspath">...
or:
<xsl:number count="$apppath" format="a" level="any"/>
The variables all throw the same error: XTSE0340 XSLT Pattern syntax error at char 0 on line 635 in {$glosspath}: A variable reference is not allowed in an XSLT pattern (except in a predicate)
I've looked at various answers on the site like this and at the guidelines, but I'm not sure how to re-write this to be compliant...
Many thanks in advance.
I believe for your template match you can do this...
<xsl:template match="*[. intersect $glosspath]">
And for the number, you can do this (although this is not necessarily as efficient)
<xsl:number count="*[. intersect $apppath]" format="a" level="any"/>
EDIT: Changed to use intersect. Thanks to Martin Honnen

How can I share match patterns between keys?

I have two keys with the same match pattern. The pattern is long. The pattern itself doesn't matter; the problem is the long duplication:
<xsl:key name="narrow-things-by-columnset" match="p | p-cont |
heading[not(parent::section or parent::contents) and not(parent::p)] |
language-desc | country-desc | graphic[not(parent::section or parent::contents)] |
block-quote | bulleted-list | blank-line |
bibliography | language-name-index | language-code-index | country-index | table-of-contents"
use="sileth:columnset-id(.)"/>
<!-- TODO: DRY: I would love to be able to share the above match pattern instead of
duplicating it. -->
<xsl:key name="narrow-things-by-section" match="p | p-cont |
heading[not(parent::section or parent::contents) and not(parent::p)] |
language-desc | country-desc | graphic[not(parent::section or parent::contents)] |
block-quote | bulleted-list | blank-line |
bibliography | language-name-index | language-code-index | country-index | table-of-contents"
use="sileth:section-id(.)"/>
The DRY principal reminds us that when we have duplication of data, we run into problems keeping the multiple copies synchronized. Indeed that just happened to me, causing a bug that took a while to track down.
So I would like to be able to share a single, common match pattern between the two keys. AFAIK you can't do that using a variable. Is there some other way to do it?
I'd define a general entity with the pattern, and refer to it from the two locations. So the stylesheet would begin
<!DOCTYPE xsl:stylesheet [
<!ENTITY match-elements "p | p-cont
| heading[not(parent::section or parent::contents)
and not(parent::p)]
| language-desc | country-desc
| graphic[not(parent::section or parent::contents)]
| block-quote | bulleted-list | blank-line
| bibliography | language-name-index | language-code-index
| country-index | table-of-contents">
]>
<xsl:stylesheet ...>
...
And the two key uses would be:
<xsl:key name="narrow-things-by-columnset"
match="&match-elements;"
use="sileth:columnset-id(.)"/>
<!-- DONE: DRY: Isn't is nice to be able to share the above
match pattern instead of duplicating it?
Hooray for general entities! -->
<xsl:key name="narrow-things-by-section"
match="&match-elements;"
use="sileth:section-id(.)"/>
How about a two-level hierarchy of keys?
like so...
<xsl:key name="narrowable-things" match="p | p-cont |
heading[not(parent::section or parent::contents) and not(parent::p)] |
language-desc | country-desc | graphic[not(parent::section or parent::contents)] |
block-quote | bulleted-list | blank-line |
bibliography | language-name-index | language-code-index | country-index | table-of-contents"
use="'universe'"/>
<xsl:key name="narrow-things-by-columnset" match="key('narrowable-things','universe')" use="sileth:columnset-id(.)"/>
<xsl:key name="narrow-things-by-section" match="key('narrowable-things','universe')" use="sileth:section-id(.)" />

Filter and sort table based XML

I am new to xslt and have done some research and read a brief book and looked at many examples but I'm afraid I just don't get it. I've only done simple procedural coding before and I guess I'm missing something. I understand a very basic example but when I try to transform my own data I am completely lost. Boo hoo hoo. It is soooo frustrating knowing that I don't it! I really feel like a hack :(
Anyway, I generated the following XML from a table in MS Word. The table ID and Row and Column IDs of each cell are given so it is possible to know how things relate to each other.
Now I want to present the data in a pick list and basically want to:
1. Filter the data on, say [Name='p_fld_parent_ref' and Value='RM12']. The data with the matching "rows" (i.e. all the nodes with the matching RowID) is what I want.
2. I also want to sort that filtered data on the column (cell) with name [p_fld_date_received]. I included a #dateSerial attribute specifically to make the sorting easier.
In the example data I should get any "rows" of data with a parent reference of 'RM12' sorted by date received. I want to use the data in the [p_fld_quantity_available] cell.
I've wasted about 3 days on this and gotten absolutely nowhere. Normally you start to get somewhere but with xslt I've gotten nowhere. Strange.
Here is one "row" of my data (sorry, I pasted the XML but don't know how to format it for reading - can someone let me know how to display it in the right format? Thanks.):
<Root><Data><Element><Name>p_fld_ref</Name><Key>SKU1</Key><KeyType>P</KeyType><ID>1</ID><Value>SKU1</Value><Description>Ref</Description><Required>True</Required><dataType>SKUn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>1</ColumnID></Element><Element><Name>p_fld_parent_ref</Name><Key>SKU1</Key><KeyType>F</KeyType><ID>2</ID><Value>RM12</Value><Description>Parent Ref</Description><Required>True</Required><dataType>CPLn,RMn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>2</ColumnID></Element><Element><Name>p_fld_item_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>3</ID><Value>ZRMH06</Value><Description>Item Code</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>3</ColumnID></Element><Element><Name>p_fld_type</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>4</ID><Value>RM</Value><Description>Type</Description><Required>True</Required><dataType>CA or RM</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>4</ColumnID></Element><Element><Name>p_fld_serial_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>5</ID><Value>120201</Value><Description>Serial No.</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>5</ColumnID></Element><Element><Name>p_fld_name_1</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>6</ID><Value>Product Name 1</Value><Description>Name 1</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>6</ColumnID></Element><Element><Name>p_fld_name_2</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>7</ID><Value>Product Name 1 Lang 2</Value><Description>Name 2</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>7</ColumnID></Element><Element><Name>p_fld_location</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>8</ID><Value/><Description>Location</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>8</ColumnID></Element><Element><Name>p_fld_receipt_no</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>9</ID><Value/><Description>Receipt No.</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>9</ColumnID></Element><Element><Name>p_fld_supplier</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>10</ID><Value/><Description>Supplier</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>10</ColumnID></Element><Element><Name>p_fld_supplier_ref</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>11</ID><Value/><Description>Supplier Ref</Description><Required>True</Required><dataType>Sn</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>11</ColumnID></Element><Element><Name>p_fld_supplier_batchno</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>12</ID><Value/><Description>Supplier Batch No.</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>12</ColumnID></Element><Element><Name>p_fld_supplier_coa</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>13</ID><Value/><Description>CoA Number</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>13</ColumnID></Element><Element><Name>p_fld_box_sample</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>14</ID><Value/><Description>Box to sample</Description><Required>True</Required><dataType>Text</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>14</ColumnID></Element><Element><Name>p_fld_number_boxes</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>15</ID><Value/><Description>Number of Containers</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>15</ColumnID></Element><Element><Name>p_fld_quantity_total</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>16</ID><Value/><Description>Total Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>16</ColumnID></Element><Element><Name>p_fld_date_received</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>17</ID><Value dateSerial="20130217000000">17-Feb-2013</Value><Description>Date Received</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>17</ColumnID></Element><Element><Name>p_fld_date_retest</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>18</ID><Value dateSerial="20140217000000">17-Feb-2014</Value><Description>Re-Test Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>18</ColumnID></Element><Element><Name>p_fld_date_expire</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>19</ID><Value dateSerial="20160217000000">17-Feb-2016</Value><Description>Expiry Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>19</ColumnID></Element><Element><Name>p_fld_date_released</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>20</ID><Value dateSerial="20130217000000">17-Feb-2013</Value><Description>Release Date</Description><Required>True</Required><dataType>Date</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>20</ColumnID></Element><Element><Name>p_fld_quantity_reserved</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>21</ID><Value>20000</Value><Description>Reserved Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>21</ColumnID></Element><Element><Name>p_fld_quantity_used</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>22</ID><Value>0</Value><Description>Used Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>22</ColumnID></Element><Element><Name>p_fld_quantity_available</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>23</ID><Value>20000</Value><Description>Available Quantity</Description><Required>True</Required><dataType>Number</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>23</ColumnID></Element><Element><Name>p_fld_status</Name><Key>SKU1</Key><KeyType>D</KeyType><ID>24</ID><Value/><Description>Status</Description><Required>True</Required><dataType>Q, R or X</dataType><parmType>1</parmType><TableID>6</TableID><RowID>3</RowID><ColumnID>24</ColumnID></Element></Root>
Assuming that each row is represented by a <Data> element (incidentally, you're missing the end tag of the <Data> element in this sample), this should do the filter/sorting part:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Root">
<Root>
<xsl:apply-templates
select="Data[Element[Name='p_fld_parent_ref']/Value='RM12']">
<xsl:sort select="Element[Name='p_fld_date_received]/Value/#dateSerial" />
</xsl:apply-templates>
</Root>
</xsl:template>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You'll need to define a template for how you want your <Data> elements presented, and probably something other than simply re-creating the Root element as I've done above. Something as simple as this might do the trick, if you just want to output text:
<xsl:template match="Data">
<xsl:text>Item no.:</xsl:text>
<xsl:value-of select="Element[Name='p_fld_item_no']/Value" />
<xsl:text> Quantity available:</xsl:text>
<xsl:value-of select="Element[Name='p_fld_quantity_available']/Value" />
<xsl:text>
</xsl:text><!-- New line -->
</xsl:template>
This isn't a complete solution, but hopefully it'll give you enough pointers to figure out the rest.