I was provided an xslt file recently to update and I have never used xslt before. I am trying to select the latest transaction date based on specific attributes of two siblings (ActionType and Status). How would I select a condition based on two siblings and return the date from that section?
I updated my with the provided code, but it's not working:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:ms="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" encoding="utf-8" />
<xsl:template match="/"><xsl:for-each select="SupplierConnectInvoice/Invoice | SupplierConnectInvoice/ImageInvoice">
Level1,,<xsl:if test="InvoiceHeader/InvoiceType = 'Original Invoice'">VO</xsl:if><xsl:if test="InvoiceHeader/InvoiceType = 'Credit Invoice'">AD</xsl:if>,<xsl:value-of select="InvoiceHeader/Partner[#PartnerType='Supplier']/Company/CompanyCode" />,<xsl:value-of select="ms:format-date(InvoiceHeader/InvoiceDate, 'MM/dd/yyy')" />,<xsl:value-of select="InvoiceHeader/InvoiceNumber" />,
<xsl:variable name="resubmitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Re-Submitted']" as="xs:dateTime*"/>
<xsl:variable name="submitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Submitted']" as="xs:dateTime*"/>
<xsl:choose>
<xsl:when test="count($resubmitted)">
<xsl:value-of select="format-dateTime(max($resubmitted),'[M01]/[D01]/[Y0001]')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-dateTime(max($submitted),'[M01]/[D01]/[Y0001]')"/>
</xsl:otherwise>
</xsl:choose>
Source Code
<?xml version="1.0" encoding="UTF-8"?>
-<SupplierConnectInvoice xmlns=" " type="A">
-<ImageInvoice id="150351390">
-<InvoiceHeader>
<InvoiceNumber>494022</InvoiceNumber>
+<Partner PartnerType="Supplier">
+<Partner PartnerType="Buyer">
<InvoiceDate>2018-12-11</InvoiceDate>
<InvoiceType>Original Invoice</InvoiceType>
<TransportClass>Supplier_Image_Direct</TransportClass>
<Total>1483.78</Total>
<SubmittedTotal>1483.78</SubmittedTotal>
<TotalLineItems>1</TotalLineItems>
<LongDescription>TM General Adv</LongDescription>
<CurrencyCode>CAD</CurrencyCode>
+<DocumentAction>
+<DocumentAction>
-<DocumentAction>
+<Person Role="DocumentActionPerformer">
<ActionType>Forward</ActionType>
<Status>Re-Submitted</Status>
<TransactionDateTime>2019-05-21T12:54:42</TransactionDateTime>
-<DocumentAction>
+<Person Role="DocumentActionPerformer">
<ActionType>Submit</ActionType>
<Status>Re-Submitted</Status>
<LongDescription>please see the amended</LongDescription>
<TransactionDateTime>2019-05-21T12:00:42</TransactionDateTime>
-<DocumentAction>
+<Person Role="DocumentActionPerformer">
<ActionType>Dispute</ActionType>
<Status>Disputed</Status>
<LongDescription>please change</LongDescription>
<TransactionDateTime>2019-05-21T08:44:46</TransactionDateTime>
-<DocumentAction>
+<Person Role="DocumentActionPerformer">
<ActionType>Submit</ActionType>
<Status>Submitted</Status>
<LongDescription>Uploaded on 05/17/2019 at 03:53:40 PM MDT</LongDescription>
<TransactionDateTime>2019-05-17T15:54:43</TransactionDateTime>
Expected results:
If there is ActionType = Submit & Status = Re-Submitted then 05/23/2019 (this would be the latest date of this combination)
If there is no Submit/Re-Submit (in other words ActionType = Submit & Status = Submitted) then 05/17/2019
Actual results is that the latest transaction date is pulled irrespective of the ActionType Status combination when I don't put in the max function.
I error out when the max function on the date
Please and thank you for your help!
This is difficult to understand.
Assuming that your input actually looks something like this:
XML
<DocumentAction>
<ActionType>Forward</ActionType>
<Status>Re-Submitted</Status>
<TransactionDateTime>2019-05-25T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Re-Submitted</Status>
<TransactionDateTime>2019-05-23T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Re-Submitted</Status>
<TransactionDateTime>2019-05-21T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Submitted</Status>
<TransactionDateTime>2019-05-19T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Submitted</Status>
<TransactionDateTime>2019-05-17T12:00:42</TransactionDateTime>
</DocumentAction>
you could use the following stylesheet:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DocumentAction">
<xsl:variable name="resubmitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Re-Submitted']" as="xs:dateTime*"/>
<xsl:variable name="submitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Submitted']" as="xs:dateTime*"/>
<output>
<xsl:choose>
<xsl:when test="count($resubmitted)">
<xsl:value-of select="format-dateTime(max($resubmitted),'[M01]/[D01]/[Y0001]')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-dateTime(max($submitted),'[M01]/[D01]/[Y0001]')"/>
</xsl:otherwise>
</xsl:choose>
</output>
</xsl:template>
</xsl:stylesheet>
to return:
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>05/23/2019</output>
which is the latest date whose ActionType = 'Submit' AND Status = 'Re-Submitted'.
Demo: https://xsltfiddle.liberty-development.net/3NJ38ZJ
If you remove these actions, so that the input is:
XML
<DocumentAction>
<ActionType>Forward</ActionType>
<Status>Re-Submitted</Status>
<TransactionDateTime>2019-05-25T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Submitted</Status>
<TransactionDateTime>2019-05-19T12:00:42</TransactionDateTime>
<ActionType>Submit</ActionType>
<Status>Submitted</Status>
<TransactionDateTime>2019-05-17T12:00:42</TransactionDateTime>
</DocumentAction>
the result will be:
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>05/19/2019</output>
which is the latest date whose ActionType = 'Submit' AND Status = 'Submitted'.
Demo: https://xsltfiddle.liberty-development.net/3NJ38ZJ/1
Added:
A more elegant version of the same idea:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/DocumentAction">
<xsl:variable name="resubmitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Re-Submitted']" as="xs:dateTime*"/>
<xsl:variable name="submitted" select="TransactionDateTime[preceding-sibling::ActionType[1]='Submit' and preceding-sibling::Status[1]='Submitted']" as="xs:dateTime*"/>
<xsl:variable name="latest-dateTime" select="if (count($resubmitted)) then max($resubmitted) else max($submitted)"/>
<output>
<xsl:value-of select="format-dateTime($latest-dateTime,'[M01]/[D01]/[Y0001]')"/>
</output>
</xsl:template>
</xsl:stylesheet>
I am trying to use streaming (XSLT 3.0) to process the report output because of huge volume. I have to use XSLT because of specific output format. While transforming the report output, I need to lookup "Supplier_Invoice_number" based on a combination of Customer_Invoice and Supplier stored in "Customer_Invoice_and_Supplier" at the line level. I need to show "Supplier_Invoice_number" at the line level.
I have created XSLT maps to store the value of "Customer_Invoice_and_Supplier" and "Supplier_Invoice_number" so that while transforming the lines i can fetch "Supplier_Invoice_number" to then show at the line level in the output. Below is the XSLT I have created, but I am getting blank for "Supplier_Invoice_number". I am new to XSLT maps and streaming, therefore would really appreciate if someone can guide me to the solution.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:map="http://www.w3.org/2005/xpath-functions/map" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:wd="urn:com.workday/bsvc" xmlns:wd1="urn:com.workday.report/INT1109_CR_REV_Lookup_Supplier_Invoice_for_Customer_Invoice" xmlns:wd2="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect" exclude-result-prefixes="xs" version="3.0">
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="SupplierInvoiceLookup CurrentLookupValue"/>
<xsl:output method="text"/>
<xsl:accumulator name="CurrentLookupValue" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="wd1:Customer_Invoice_and_Supplier/text()" select="."/>
</xsl:accumulator>
<xsl:accumulator name="SupplierInvoiceLookup" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="wd1:Supplier_Invoice_Number/text()" select="map:put($value, string(.), accumulator-before('CurrentLookupValue'))"/>
</xsl:accumulator>
<xsl:strip-space elements="*"/>
<xsl:template match="AggregatedData">
<xsl:for-each select="wd2:Report_Data/wd2:Report_Entry/copy-of()">
<xsl:text>{
"Company": "</xsl:text>
<xsl:value-of select="wd2:Company"/>
<xsl:iterate select="wd2:lines">
<xsl:text> {
</xsl:text>
<xsl:text> "sequence": "</xsl:text>
<xsl:value-of select="wd2:sequence"/>
<xsl:text>",
</xsl:text>
<xsl:text> "sales_item_id": "</xsl:text>
<xsl:value-of select="wd2:sales_item_id"/>
<xsl:text>",
</xsl:text>
<xsl:text> "supplier_invoice_no": "</xsl:text>
<xsl:variable name="supplier_invoice_no" select="accumulator-before('SupplierInvoiceLookup')( normalize-space( #wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:value-of select="accumulator-before('SupplierInvoiceLookup')( normalize-space( #wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:text>",
</xsl:text>
</xsl:iterate>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
Sample XML -
<?xml version="1.0" encoding="utf-8"?>
<AggregatedData>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Lookup_Supplier_Invoice_for_Customer_Invoice">
<wd:Report_Entry>
<wd:Supplier_Invoice_Lines_group>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Dell Receivables LP</wd:Customer_Invoice_and_Supplier>
<wd:Supplier_Invoice_Number>SI-00000047</wd:Supplier_Invoice_Number>
</wd:Supplier_Invoice_Lines_group>
</wd:Report_Entry>
<wd:Report_Entry>
<wd:Supplier_Invoice_Lines_group>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Charles Case [C]</wd:Customer_Invoice_and_Supplier>
<wd:Supplier_Invoice_Number>SI-00000050</wd:Supplier_Invoice_Number>
</wd:Supplier_Invoice_Lines_group>
</wd:Report_Entry>
</wd:Report_Data>
<wd:Report_Data xmlns:wd="urn:com.workday.report/INT1109_CR_REV_Customer_Invoices_to_Connect">
<wd:Report_Entry>
<wd:Company>Financial/wd:Company>
<wd:lines>
<wd:sequence>a8</wd:sequence>
<wd:sales_item_id>Data - Enterprise License</wd:sales_item_id>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Dell Receivables LP</wd:Customer_Invoice_and_Supplier>
</wd:lines>
<wd:lines>
<wd:sequence>a9</wd:sequence>
<wd:sales_item_id>TBA Trade Reports ATS Cncl</wd:sales_item_id>
<wd:Customer_Invoice_and_Supplier>INV-201900000024Charles Case [C]</wd:Customer_Invoice_and_Supplier>
</wd:lines>
</wd:Report_Entry>
</wd:Report_Data>
</AggregatedData>
For some reason you have used #wd2:Customer_Invoice_and_Supplier to select an attribute although your input has element data, so I think you want
<xsl:template match="AggregatedData">
<xsl:for-each select="wd2:Report_Data/wd2:Report_Entry/copy-of()">
<xsl:text>{
"Company": "</xsl:text>
<xsl:value-of select="wd2:Company"/>
<xsl:iterate select="wd2:lines">
<xsl:text> {
</xsl:text>
<xsl:text> "sequence": "</xsl:text>
<xsl:value-of select="wd2:sequence"/>
<xsl:text>",
</xsl:text>
<xsl:text> "sales_item_id": "</xsl:text>
<xsl:value-of select="wd2:sales_item_id"/>
<xsl:text>",
</xsl:text>
<xsl:text> "supplier_invoice_no": "</xsl:text>
<xsl:value-of select="accumulator-before('SupplierInvoiceLookup')( normalize-space( wd2:Customer_Invoice_and_Supplier ) )"/>
<xsl:text>",
</xsl:text>
</xsl:iterate>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:template>
And I think you want the map to be the other way around in terms of key and values so the accumulators would do
<xsl:accumulator name="CurrentLookupValue" as="xs:string" initial-value="''" streamable="yes">
<xsl:accumulator-rule match="wd1:Customer_Invoice_and_Supplier/text()" select="string()"/>
</xsl:accumulator>
<xsl:accumulator name="SupplierInvoiceLookup" as="map(xs:string,xs:string)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule match="wd1:Supplier_Invoice_Number/text()" select="map:put($value, accumulator-before('CurrentLookupValue'), string(.))"/>
</xsl:accumulator>
https://xsltfiddle.liberty-development.net/ncdD7mu
If you want to output JSON then note that XSLT 3 with XPath 3.1 maps and arrays and the output method json does allow that in a comfortable way without struggling to output pairs of braces and element names. But that might be better solved in a separate question where you show us the JSON you want.
Here is a toned down version of my use case. I have
XSL file for transformation
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl">
<xsl:output method="text"/>
<xsl:template match="Message">
<xsl:for-each select="ent">
<xsl:variable name="current_key" select="#key"/>
<xsl:variable name="current_type" select="#type"/>
<xsl:variable name="Match" select="exsl:node-set(msg)/ent"/>
<xsl:copy>
<xsl:copy-of select="exsl:node-set($Match)/#type"/>
<xsl:copy-of select="exsl:node-set($Match)/#key|exsl:node-set($Match)/translation/text()"/>
<!--- <xsl:copy-of select="exsl:node-set($Match)/#key|exsl:node-set($Match)/translation/text()|exsl:node-set($Match)/#type"/> Trial statement -->
</xsl:copy>
</xsl:for-each>
<xsl:call-template name = "Me" select="$Message"/>
</xsl:template>
</xsl:stylesheet>
And an input file as follows
<?xml version="1.0" encoding="utf-8"?>
<msg>
<ent key="key1" type="error">
<text>Error: Could not find </text>
<translation>Another Error similar to previous one.</translation>
</ent>
<ent key="key2" type="damage">
<text>Error2: Could not find2 </text>
<translation>Another Error2 similar to previous one.</translation>
</ent>
</msg>
I am using libXSLT in Perl as my transformation engine. My transformation script is already mentioned in this answer. Whenever I execute the script, I get the output as follows.
Error: Could not find
Another Error similar to previous one.
Error2: Could not find2
Another Error2 similar to previous one.
Why is the attribute type not getting printed? How do I retrieve it with the help of exsl:node-set or any other techniques? Also, can I include the attribute type in the trial statement in such a way that it will be in the output?
The following stylesheet:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/msg">
<xsl:for-each select="ent">
<xsl:text>KEY: </xsl:text>
<xsl:value-of select="#key"/>
<xsl:text>
TYPE: </xsl:text>
<xsl:value-of select="#type"/>
<xsl:text>
TEXT: </xsl:text>
<xsl:value-of select="text"/>
<xsl:text>
TRANSLATION: </xsl:text>
<xsl:value-of select="translation"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when applied to your input example, will produce:
KEY: key1
TYPE: error
TEXT: Error: Could not find
TRANSLATION: Another Error similar to previous one.
KEY: key2
TYPE: damage
TEXT: Error2: Could not find2
TRANSLATION: Another Error2 similar to previous one.
I am writing an xslt for datapower and in that I am getting a date(Payment Date).I have to check whether that date(Paymentt Date) is within 180 days of current date
I am getting present date by the following
<xsl:variable name="timestamp" select="date:date-time()"/>
Now how to check the condition for 180 days
Following is my xslt
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:date="http://exslt.org/dates-and-times"
xmlns:dpconfig="http://www.datapower.com/param/config"
extension-element-prefixes="dp"
exclude-result-prefixes="dp dpconfig"
>
<xsl:output method="xml"/>
<xsl:template match="/">
<PaymentDate><xsl:value-of select="dp:http-request-header('X-payment-date')"/></PaymentDate> (From request I am getting payment date)
<xsl:variable name="timestamp" select="date:date-time()"/> (From here I am getting present date)
<xsl:if (this is what I am confused)
Thanks
Calculating date difference in XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="date">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<output>
<difference>
<xsl:call-template name="date-difference">
<xsl:with-param name="date1" select="input/originalDate" />
<xsl:with-param name="date2" select="date:date-time()" />
</xsl:call-template>
</difference>
</output>
</xsl:template>
<xsl:template name="date-difference">
<xsl:param name="date1"/>
<xsl:param name="date2"/>
<xsl:param name="JDN1">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$date1" />
</xsl:call-template>
</xsl:param>
<xsl:param name="JDN2">
<xsl:call-template name="JDN">
<xsl:with-param name="date" select="$date2" />
</xsl:call-template>
</xsl:param>
<xsl:value-of select="$JDN2 - $JDN1"/>
</xsl:template>
<xsl:template name="JDN">
<xsl:param name="date"/>
<xsl:param name="year" select="substring($date, 1, 4)"/>
<xsl:param name="month" select="substring($date, 6, 2)"/>
<xsl:param name="day" select="substring($date, 9, 2)"/>
<xsl:param name="a" select="floor((14 - $month) div 12)"/>
<xsl:param name="y" select="$year + 4800 - $a"/>
<xsl:param name="m" select="$month + 12*$a - 3"/>
<xsl:value-of select="$day + floor((153*$m + 2) div 5) + 365*$y + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045" />
</xsl:template>
</xsl:stylesheet>
When applied to the following input XML:
<input>
<originalDate>2013-09-15</originalDate>
</input>
The result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<difference>179</difference>
</output>
if the transformation is performed today, 2014-03-13.
IBM Datapower provides extension to existing EXSLT function. There is a function called 'difference' with following signature.
difference()
Returns the duration between the first date and the second date.
Syntax
date:difference(start-dateTime, end-dateTime)
You can utilize this function to find out the date-difference. For more information about extension function available in datapower see below link. Go to Chapter 5 and look for functions for manipulating 'date time'.
http://pic.dhe.ibm.com/infocenter/wsdatap/v3r8m1/topic/xm70/ExtensionFunctions.pdf
-Ajitabh
DataPower Supports Date:Difference
difference()
Returns the duration between the first date and the second date.
Syntax
date:difference(start-dateTime, end-dateTime)
You can pass current date as end-DateTime , date which wants to check as start-DateTime
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs"
xmlns:date="http://exslt.org/dates-and-times" version="1.0">
<!--<xsl:key name="lang" match="element" use="#language"></xsl:key>-->
<xsl:template match="/">
<xsl:variable name="date1" select="date:date-time()"/>
<xsl:variable name="date2" select="'2013-09-15'"/>
<output>
<difference>
<xsl:value-of select="translate(date:difference($date2, $date1),'PD','')"/>
</difference>
</output>
</xsl:template>
</xsl:stylesheet>
I have a date value in a field in XML file in this format:
<Date value="4/1/2013 5:13:41 PM"/>
I want to convert it into a standard XSD format:
2013-04-01T17:13:41.000Z
How can I do that in my XSL transform? I can use both 1.0 and 2.0 stylesheet versions.
So...I was bored and hadn't played with xsl:analyze-string before. Here's a regex-based solution :
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" version="2.0">
<xsl:template match="/item/date">
<xsl:analyze-string select="#value" regex="([0-9]+)/([0-9]+)/([0-9]+) ([0-9]+):([0-9]+):([0-9]+) (PM|AM)">
<xsl:matching-substring>
<xsl:variable name="month" select="number(regex-group(1))"/>
<xsl:variable name="day" select="number(regex-group(2))"/>
<xsl:variable name="year" select="number(regex-group(3))"/>
<xsl:variable name="hours">
<xsl:choose>
<xsl:when test="regex-group(7) = 'PM'">
<xsl:value-of select="12 + number(regex-group(4))"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="number(regex-group(4))"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="minutes" select="number(regex-group(5))"/>
<xsl:variable name="seconds" select="number(regex-group(6))"/>
<xsl:variable name="dateTime" select="xs:dateTime( concat($year, '-', format-number($month, '00'), '-', format-number($day, '00'), 'T', format-number($hours, '00'), ':', format-number($minutes, '00'), ':', format-number($seconds, '00'), 'Z') )" />
<reformattedDate>
<xsl:value-of select="$dateTime"/>
</reformattedDate>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
I ran this against a test xml file like this :
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<item>
<date value="4/1/2013 5:13:41 PM"/>
</item>
And the output is this :
<?xml version="1.0" encoding="UTF-8"?>
<reformattedDate xmlns:xs="http://www.w3.org/2001/XMLSchema">2013-04-01T17:13:41Z</reformattedDate>
If you want to format the output more precisely, as already recommended, you can use the format-date function.
Here is an XSLT 1.0 version, using substring-before and substring-after.
Thanks to adhocgeek for the XML input.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/item">
<xsl:copy>
<xsl:apply-templates select="date"/>
</xsl:copy>
</xsl:template>
<xsl:template match="date">
<xsl:copy>
<xsl:variable name="date" select="substring-before(#value, ' ')"/>
<xsl:variable name="M" select="substring-before($date, '/')"/>
<xsl:variable name="D-Y" select="substring-after($date, '/')"/>
<xsl:variable name="D" select="substring-before($D-Y, '/')"/>
<xsl:variable name="Y" select="substring-after($D-Y, '/')"/>
<xsl:variable name="time-ampm" select="substring-after(#value, ' ')"/>
<xsl:variable name="time" select="substring-before($time-ampm, ' ')"/>
<xsl:variable name="ampm" select="substring-after($time-ampm, ' ')"/>
<xsl:variable name="h" select="substring-before($time, ':')"/>
<xsl:variable name="m-s" select="substring-after($time, ':')"/>
<xsl:variable name="m" select="substring-before($m-s, ':')"/>
<xsl:variable name="s" select="substring-after($m-s, ':')"/>
<xsl:variable name="hh">
<xsl:choose>
<xsl:when test="$ampm = 'PM'">
<xsl:value-of select="format-number($h + 12, '00')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($h, '00')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="concat($Y, '-', $M, '-', $D, 'T', $hh, ':', $m, ':', $s)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
XML
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<item>
<date value="4/1/2013 5:13:41 PM"/>
</item>
output
<?xml version="1.0" encoding="utf-8"?>
<item>
<date>2013-4-1T05:13:41</date>
</item>
Have a look at ESXLT. Many XSL implementations already support the functions out of the box, and they provide fallbacks in case yours does not support it.
If you have fully-compliant implementation, you can do this:
<?xml ...
xmlns.date="http://exslt.org/dates-and-times"
...
<xsl:variable name="xsdate" select="date:parse-date(Date/#value, 'M/d/yyyy h:mm:ss a')" />
<xsl:value-of select="$xsdate" />
I cheated and used the result of date:parse directly. One often uses the date:format function to format the date into a particular format.
I've honestly been struggling with getting the "fallback" functions from EXSLT working for me in Apache Xalan. (Using non-core EXSLT date functions with Xalan Java)
IMO it would be better to have the date in xs:dateTime format in the first place, and convert to some localized format afterward. You can use the date:format function to format the date as necessary, and the date:format function enjoys much wider support amongst XSLT processors.
Using XSLT 2.0 you can construct an xs:dateTime value by extracting the components from Date/#value using string functions, then you can format if needed using format-dateTime: http://www.w3.org/TR/xslt20/#format-date. Extracting the date components like year, month, day should be straightforward, obviously for the time you need to some more work to pay attention to the PM or AM 12-hour clock.
#adhockgeek's answer worked almost perfectly for me. I only had to add a condition so that it not add 12 to the hour if the value was currently 12. IE, 12:15 PM != 24:15 AM.
I created a template from his code that can be called repeatedly within a transform:
<xsl:template name="FormatDate">
<xsl:param name="dt"/>
<xsl:analyze-string select="$dt" regex="([0-9]+)/([0-9]+)/([0-9]+) ([0-9]+):([0-9]+):([0-9]+) (PM|AM)">
<xsl:matching-substring>
<xsl:variable name="month" select="number(regex-group(1))"/>
<xsl:variable name="day" select="number(regex-group(2))"/>
<xsl:variable name="year" select="number(regex-group(3))"/>
<xsl:variable name="hours">
<xsl:choose>
<xsl:when test="regex-group(7) = 'PM' and regex-group(4) != '12'">
<xsl:value-of select="12 + number(regex-group(4))"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="number(regex-group(4))"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="minutes" select="number(regex-group(5))"/>
<xsl:variable name="seconds" select="number(regex-group(6))"/>
<xsl:variable name="dateTime" select="xs:dateTime(concat($year, '-', format-number($month, '00'), '-', format-number($day, '00'), 'T', format-number($hours, '00'), ':', format-number($minutes, '00'), ':', format-number($seconds, '00')))" />
<xsl:value-of select="$dateTime"/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
Code with substring-before and substring-after works, but more checks should be added:
12AM => 00 and 12PM => 12 something like this:
<xsl:variable name="hh">
<xsl:choose>
<xsl:when test="$ampm = 'AM' and $h='12'">00</xsl:when>
<xsl:when test="$ampm = 'PM' and $h!='12'">
<xsl:value-of select="format-number($h + 12, '00')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number($h, '00')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>