WSo2 Time converter for 12 to 24 hour format - xslt

How to convert 12 hour format time in to 24 hour Format as a Property in the WSO2 ESB
sample time : 6:00PM
Converted time : 1800

WSO2 does not have an OOB solution for this. But there are multiple ways to implement this.
Using XSLT mediator.
You can use the following XSLT to convert your time to 24Hours format. Note that the following XSLT is tightly coupled to the sample formats you provided. If you have a different format you may need to alter the XSLT accordingly.
<?xml version="1.0" encoding="UTF-8"?>
<localEntry key="LOCAL_XSLT_Time" xmlns="http://ws.apache.org/ns/synapse">
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output encoding="UTF-8" indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:param name="timeToConv"/>
<xsl:template match="/">
<!-- Iterating through message elements -->
<time>
<xsl:call-template name="time-to-24H">
<xsl:with-param name="timeToConvert" select="$timeToConv"/>
</xsl:call-template>
</time>
</xsl:template>
<xsl:template name="time-to-24H">
<xsl:param name="timeToConvert"/>
<xsl:variable name="hours" select="substring-before($timeToConvert, ':')"/>
<xsl:variable name="minWithComp" select="substring-after($timeToConvert, ':')"/>
<xsl:variable name="min" select="substring($minWithComp, 1, string-length($minWithComp) - 2)"/>
<xsl:variable name="comp" select="substring($minWithComp, string-length($minWithComp) - 1, string-length($minWithComp))"/>
<xsl:variable name="hourPart" select="floor($hours mod 12 + 12 * number($comp='PM'))"/>
<xsl:value-of select="$hourPart"/>
<xsl:value-of select="$min"/>
</xsl:template>
</xsl:stylesheet>
</localEntry>
You can invoke this XSLT using the XSLT mediator. Read more on the XSLT mediator from here.
<xslt key="LOCAL_XSLT_Time">
<property name="timeToConv" value="6:00PM"/>
</xslt>
Then after the XSLT mediator, you can access converted time like below.
<property name="convertedTime" expression="//time/text()"></property>
This question has more details on how to use the XSLT mediator.
2 Using the script mediator.
You can use the Script mediator with JavaScript to convert your time. There are plenty of JavaScript samples out there for this. You can refer this to understand how to use the script mediator.

Related

Transform to output xml based on the value of an element in the input xml

I am trying to learn the basics of XSLT, but am stuck on a particular use case. What I want to achieve is to transform one xml file into another xml (I am using XSLT 2.0), but a condition is that the grouping of elements in the output xml is decided by the value of one particular element in the input xml.
I will try to exemplify my question through a made-up example.
Lets say this is an input xml:
<products>
<shoes>
<shoe>
<name>Ecco City</name>
<category>Urban</category>
</shoe>
<shoe>
<name>Timberland Forest</name>
<category>Wildlife</category>
</shoe>
<shoe>
<name>Asics Gel-Kayano</name>
<category>Running</category>
</shoe>
</shoes>
<clothes>
<shorts>
<name>North Face</name>
<category>Wildlife</category>
</shorts>
<shorts>
<name>Adidas Running Shorts</name>
<category>Running</category>
</shorts>
</clothes>
Based on the value of the category element I want to, for each product, list similar products, that is, other products having the same category in the input xml, like this:
<output>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
</output>
This doesn't seem to be a grouping problem as such. If I understand correctly, you want to do something like:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="product-by-category" match="*" use="category" />
<xsl:template match="/products">
<output>
<xsl:for-each select="*/*">
<forSale>
<item>
<xsl:value-of select="name" />
</item>
<xsl:for-each select="key('product-by-category', category) except .">
<similarItem>
<xsl:value-of select="name" />
</similarItem>
</xsl:for-each>
</forSale>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Applied to your input example, the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<output>
<forSale>
<item>Ecco City</item>
</forSale>
<forSale>
<item>Timberland Forest</item>
<similarItem>North Face</similarItem>
</forSale>
<forSale>
<item>Asics Gel-Kayano</item>
<similarItem>Adidas Running Shorts</similarItem>
</forSale>
<forSale>
<item>North Face</item>
<similarItem>Timberland Forest</similarItem>
</forSale>
<forSale>
<item>Adidas Running Shorts</item>
<similarItem>Asics Gel-Kayano</similarItem>
</forSale>
</output>

XSL query inner xml

Say this is my xml :
<History>
<University>TSU</University>
<Payload>
<Attrib Order="0">OVERSEA</Attrib>
<Attrib Order="1">GRADE2</Attrib>
<Attrib Order="2"><Person><ID>TQR344</ID></Person></Attrib>
<Attrib Order="3">3566644</Attrib>
</Payload>
</History>
And I want to query the inner XML inside Order=2 tag and read ID of the person.
I have created this so far :
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes" omit-xml-declaration="no" />
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
<xsl:template name="Person" match="//History">
<Event>
<Uni><xsl:value-of select="University" /></Uni>
<ID><xsl:value-of select="Payload/Attrib[#Order='2']/Person/ID" disable-output-escaping="yes" /></ID>
</Event>
</xsl:template>
</xsl:stylesheet>
But as you can see it is not working.
Also I assigned the inner XML into a variable and tried to query that variable and It didn't work too.
Is it possible to do that via xsl ?
Limitations : I cannot change xml format. But maybe I was able to move from xsl ver 1 to new versions.
I want to query the inner XML inside Order=2 tag
The tag in question does not contain any XML; its content is a string and needs to be manipulated using string functions. Try:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/History">
<Event>
<Uni>
<xsl:value-of select="University" />
</Uni>
<ID>
<xsl:value-of select="substring-before(substring-after(Payload/Attrib[#Order='2'], '<ID>'),'</ID><')"/>
</ID>
</Event>
</xsl:template>
</xsl:stylesheet>
Note:
1. This:
<xsl:template match="/History">
<xsl:apply-templates select="/History" />
</xsl:template>
creates an infinite loop and will crash your processor.
2. Alternatively, you could serialize the string back into XML and process the result as XML; in XSLT 1.0, this can be done only by outputting the string with the escaping disabled, saving the result as a new document, then processing the new document with another XSLT stylesheet. Using XSLT 3.0 (or a processor that supports serializing as an extension) this can be all done during the same transformation.

Find node containing the most recent date when date needs converted to valid date format

I need an xPath to be used in a global variable which will select the 'Policy' node with the most recent dateTime (2014-12-02-04:00). Unfortanately the Time delimeter is a dash instead of 'T' so I can't use max() straight away. If I try to use substring or translate to remove the dashes and colon to simply compare numbers I get the error which states that there cannot be more that one sequence in those functions.
Is there a way to evaluate PolicyEffectiveDate from the root node when it is in 2014-12-02-04:00 format?
/Policies/PolicySummary/Policy[2]/PolicyEffectiveDate
XSLT 2.0 is OK. Also, note that I don't have control over the XML format. Thanks.
Given sample XML of:
<?xml version="1.0" encoding="UTF-8"?>
<Policies>
<PolicySummary>
<Policy>
<PolicyNumber>123</PolicyNumber>
<PolicyEffectiveDate>2014-06-01-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
<Policy>
<PolicyNumber>12345</PolicyNumber>
<PolicyEffectiveDate>2014-08-02-04:00</PolicyEffectiveDate>
</Policy>
</PolicySummary>
</Policies>
You can simply sort the policies by their "dates" as text. For example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:for-each select="Policies/PolicySummary/Policy">
<xsl:sort select="PolicyEffectiveDate" data-type="text" order="descending"/>
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
will return:
<?xml version="1.0" encoding="utf-8"?>
<Policy>
<PolicyNumber>1234</PolicyNumber>
<PolicyEffectiveDate>2014-12-02-04:00</PolicyEffectiveDate>
</Policy>
in your example.

XSLT issue with reading in non-xml data

I am newbie to XSLT.
I have a requirement to read a URL and convert some of its values into XML.
I wrote a XSLT that has to take URL as the input value and create a XML file from some of the content of the URL value.
When I debugged the XSLT in XMLSPY, I noticed that the URL value is not being picked up by inputValue variable in the below code. I am not sure if my approach to input the URL and the template match are wrong.
Any help is appreciated.
Thanks in advance.
Input to XSLT:
http://host:port/abc/xyz1/6xyz6?qq=123&pp=3
Here the XSLT:
<?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:nnc="Nnc" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:param name="inVal" select="xs:string(http://host:port/abc/xyz1/6xyz6?qq=123&pp=3)"/>
<xsl:template match="/">
<xsl:variable name="inputValue" select="$inVal"/>
<xsl:if test="string-length($inputValue)=0">
<xsl:message terminate="yes">
inputValue is blank
</xsl:message>
</xsl:if>
<xsl:variable name="value" as="xs:string" select="substring-after($inputValue, 'abc/' )"/>
<xsl:variable name="tokenizedValues" select="tokenize($value,'/')"/>
<xsl:for-each select="$tokenizedValues">
<xsl:if test="position() = 1">
<id>
<xsl:value-of select="."/>
</id>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The desired XML output:
<?xml version="1.0" encoding="UTF-8"?>
<id>6xyz6</id>
<qq>123</qq>
<pp>123</pp>
Well if you want to pull in a text file then with XSLT 2.0 and later you can do that but not by simply using a URL, you need to call the unparsed-text function e.g.
<xsl:variable name="inputData" as="xs:string" select="unparsed-text('http://example.com/foo')"/>
See http://www.w3.org/TR/xslt20/#unparsed-text, depending on the encoding of your text document you need to add a second parameter when calling the function.

XSLT 2.0 Xpath error "not a node item"

my first post here - sure hope someone will know the answer!
I have been able to find solutions for many issues I had, but not for this one.
The questions and answers on this site on the same subject did not solve my issue...
I have an xml containing Format specifications like
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
TagNr + option is a unique combination within this nodeset.
I defined a key to make using the set of formats easier:
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
I can indeed use this key and get the correct value, but only in non-special elements; I get an error "Error in XPath 2.0 expression Not a node item" when using this key within for-each or other constructs like the one below.
What I try to achieve is the following: In other nodes processed there is a string of options for which I wish to retrieve the format for each character.
For example:
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
I have been trying lots of variants of the below but no luck:
<xsl:variable name="TN"><xsl:value-of select="TagNr"/></xsl:variable>
<xsl:variable name="optList">
<xsl:analyze-string select="./Options" regex="[A-Z]">
<xsl:matching-substring>
<xsl:variable name="TNO" select="concat($TN, .)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
Splitting into individual characters using the regex goes fine and when retrieving (only) the value for opt/tag that goes fine too.
But when I add opt/fmt, I run into the mentioned error message for the Xpath expression select="key('xx', $TNO)".
I tried defining a variable based on the key function as suggested in another thread on this site, but did not succeed.
Can anyone help me?
The key() function (with two arguments) searches the document containing the context node. If the context item is not a node - for example, within analyze-string - then you will get this error, because it doesn't know which document to search. The answer is to use the third argument of key() to supply this information.
The problem is that the context changes in your analyze-string element. Maybe the following solution will help you.
For an XML file like that :
<a>
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
</a>
Consider the following XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
<xsl:template match="/">
<result>
<xsl:apply-templates select="//Tag"/>
</result>
</xsl:template>
<xsl:template match="Tag">
<xsl:call-template name="createOPT">
<xsl:with-param name="str" as="xs:string" select="Options"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="createOPT">
<xsl:param name="str"/>
<xsl:if test="string-length($str) > 0">
<xsl:variable name="firstChar" select="substring($str,1,1)"/>
<xsl:variable name="TNO" select="concat(TagNr,$firstChar)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
<xsl:call-template name="createOPT">
<xsl:with-param name="str" select="substring($str,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The result is :
<?xml version="1.0" encoding="UTF-8"?>
<result>
<opt>
<tag>92A</tag>
<fmt>//[N]15d</fmt>
</opt>
<opt>
<tag>92B</tag>
<fmt>//3!a/3!a/15d</fmt>
</opt>
</result>
The easiest XSLT 2.0 way to process a string character by character is the following:
<xsl:for-each select="string-to-codepoints($vStr)">
<xsl:variable name="$vChar" select=
"codepoints-to-string(.)"/>
<!-- Process $vChar here: -->
</xsl:for-each/>
You can combine this with saving the original document context into a variable (say $vDoc) and using this variable as the 3rd argument of the key() function -- which is again an XSLT 2.0 - only feature.
So, you'll have something like:
key('xx', concat($TN, $vChar), $vDoc)
Summary:
Use the string-to-codepoints() and codepoints-to-string() functions for char-by-char processing.
Use the 3-rd argument of the key() function to specify context different from the current.