Modify date as per matching a attribute name value in XSLT - xslt

I have an array of element where a:value element can have different values in it. In case the element contains date in Zulu format i.e.: 2019-04-17T10:42:48.0135859, I need to change it to YYYY-MM-DD format. I have already come up with a solution. However, I am more interested in the matching i:type="b:dateTime" in my condition. Which means if i:type is equal to or contains b:dateTime then the XSLT will fetch the date and do the required transformation.
The input XML is:
<Properties
xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
>
<a:KeyValueOfstringanyType>
<a:Key>dtDynamicModifyDate</a:Key>
<a:Value i:type="b:dateTime"
xmlns:b="http://www.w3.org/2001/XMLSchema"
>2019-04-17T10:42:48.0135859</a:Value>
</a:KeyValueOfstringanyType>
<a:KeyValueOfstringanyType>
<a:Key>tiEnrollmentStatus</a:Key>
<a:Value i:type="b:string"
xmlns:b="http://www.w3.org/2001/XMLSchema"
>Enrolled</a:Value>
</a:KeyValueOfstringanyType>
<a:KeyValueOfstringanyType>
<a:Key>tiNumberOfEnrollments</a:Key>
<a:Value i:type="b:int"
xmlns:b="http://www.w3.org/2001/XMLSchema"
>1</a:Value>
</a:KeyValueOfstringanyType>
<a:KeyValueOfstringanyType>
<a:Key>dtModifyDate</a:Key>
<a:Value i:type="b:dateTime"
xmlns:b="http://www.w3.org/2001/XMLSchema"
>2019-04-16T15:57:39.331-04:00</a:Value>
</a:KeyValueOfstringanyType>
</Properties>
The transformation is available here: https://xsltfiddle.liberty-development.net/ncdD7mC/1
Instead of this condition, I want the above condition to be checked (i:type is equal to or contains b:dateTime)
<xsl:when test="contains($payload/*[local-name()='Value'], '-') and contains($payload/*[local-name()='Value'], 'T') and contains($payload/*[local-name()='Value'], ':')">
Any pointer for the XPATH will be appreciated.
Cheers,
Sierra

The expression I think you are looking for is this...
<xsl:when test="$payload/*[local-name()='Value']/#*[name()='i:type'] ='b:dateTime'">
However, this would fail if the namespace prefix changed, so perhaps you should do this:
<xsl:when test="$payload/*[local-name()='Value']/#*[local-name()='type'] ='b:dateTime'">
But this could potentially not give you the right results if you had two attributes named type in different namespace. The only real solution is to declare the xmlns:i namespace in the XSLT, then you would do this:
<xsl:when test="$payload/*[local-name()='Value']/#i:type ='b:dateTime'">

Related

How to use contains() with a set of strings in XSLT

I have the following XML snippet:
<figure customer="ABC DEF">
<image customer="ABC"/>
<image customer="XYZ"/>
</figure>
I'd like to check if the figure element's customer attribute contains the customer attributes of the image elements.
<xsl:if test="contains(#customer, image/#customer)">
...
</xsl:if>
I get an error saying:
a sequence of more than one item is not allowed as the second argument of contains
It's important to note that I cannot tell the values of the customer attributes in advance, thus using xsl:choose is not an option here.
Is it possible to solve this without using xsl:for-each?
In XSLT 2.0 you can use:
test="image/#customer/contains(../../#customer, .) = true()"
and you will get a true() result if any of them are true. Actually, that leads me to suggest:
test="some $cust in image/#customer satisfies contains(#customer, $cust)"
but that won't address the situation where the customer string is a subset of another customer string.
Therefore, perhaps this is best:
test="tokenize(#customer,'\s+') = image/#customer"
... as that will do a string-by-string comparison and give you true() if any of the tokenized values of the figure attribute is equal to one of the image attributes.

Using <xsl:for-each> for incremental element names

I have an XML that is converted from a java map. So all the map keys are converted into node names. The XML structure is as below
<map>
<firstName>AAA</firstName>
<firstName1>BBB</firstName1>
<firstName2>CCC</firstName2>
<firstName3>DDD</firstName3>
</map>
I am trying to write a for-each loop to extract data from this XML to create an output XML. I have tried most of the options available such as name(), local-name(), contains(), etc but couldn't come up with something that worked. What are the options available since the incremental node name can go upto count 100 or more. Any inputs in coding the loop would be of great help. I am using XSLT 1.0.
There are many ways to select the children of the top element (map):
/*/*
This selects all elements that are children of the top element of the XML document.
/*/*[starts-with(name(), 'firstName')]
This selects all top element's children-elements, whose name starts with the string 'firstName'.
/*/*[starts-with(name(), 'firstName')
and floor(substring-after(name(), 'firstName')) = substring-after(name(), 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this is an integer.
/*/*[starts-with(name(), 'firstName')
and translate(name(), '0123456789', '') = 'firstName')) ]
This selects all top element's children-elements, whose name starts with the string 'firstName' and the remaining substring after this contains only digits.
Finally, in XPath 2.0 (XSLT 2.0) one can use regular expressions:
/*/*[matches(name(), '^firstName\d+$')]
This will select all the first level elements and their information, which you can then use as you wish:
<xsl:for-each select="/*/*">
<xsl:value-of select="local-name()"/>
<xsl:value-of select="."/>
</xsl:for-each>

Saxon error "XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string"

I have an XSL program which in turn generates an XSL program, which depending on the input might look like this:
<xsl:variable name="patterns"/> <!--empty in this particular case-->
<xsl:template name="token">
<xsl:for-each select="$patterns/pattern">
...
When I then run the generated stylesheet, Saxon, bless its heart, is apparently doing some kind of static analysis and complains:
XPTY0019: Required item type of first operand of '/' is node(); supplied value has item type xs:string
and won't even compile the stylesheet.
My workaround was to generate a dummy element in the $patterns nodeset, but is there any cleaner approach here, or way to suppress the compile error?
According to http://www.w3.org/TR/xslt20/#variable-values, "If the variable-binding element has empty content and has neither a select attribute nor an as attribute, then the supplied value of the variable is a zero-length string.".
So you need to change that, for instance by doing <xsl:variable name="patterns" select="()"/> to bind an empty sequence as the variable value.
In XSLT 1.0 (the same would work also with XSLT 2.0) use:
<xsl:variable name="patterns" select="/.."/>
This provides to the XSLT processor the information, necessary to conclude that the type of the $patterns variable is node-set.

Break the XSLT for-each loop when first match is found

I am having trouble to display the first matching value, like
<test>
<p>30</p>
<p>30{1{{23{45<p>
<p>23{34</p>
<p>30{1{98</p>
</test>
<test2>
<p1>text</p1>
</test2>
So i want to loop through the <test></test> and find the value of <p> node whose string length is greater than 2 and that contains 30. I want only the first value.
so i tired the following code
<xsl:variable name="var_test">
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
</xsl:variable>
the problem is the var_test is being null always.
if i try directly with out any variable
<xsl:for-each select="*/*/test()>
<xsl:if string-length(p/text())>2 and contains(p/text(),'30'))
<xsl:value-of select="xpath">
I am getting the following output
<p>30{1{23{4530{1{98</p>
but the desired output is
<p>0{1{23{45</p>
so how can i achieve this?
Instead of the for-each, use
<xsl:copy-of select="(*/*/test/p[string-length() > 2 and
contains(.,'30'))] )[1]" />
The [1] selects only the first matching <p>. (Updated: I changed the XPath above in response to #markusk's comment.)
The above will output that <p> element as well as its text content, as shown in your "desired output". If you actually want only the value of the <p>, that is, its text content, use <xsl:value-of> instead of <xsl:copy-of>.
Addendum:
The idea of breaking out of a loop does not apply to XSLT, because it is not a procedural language. In a <xsl:for-each> loop, the "first" instantiation (speaking in terms of document order, or sorted order) of the loop is not necessarily evaluated at a time chronologically before the "last" instantiation. They may be evaluated in any order, or in parallel, because they do not depend on each other. So trying to "break out of the loop", which is intended to cause "subsequent" instantiations of the loop not to be evaluated, cannot work: if it did, the outcome of later instantiations would be dependent on earlier instantiations, and parallel evaluation would be ruled out.

Assigning parameter value to the xsl: for each

Can anybody who has worked with XSLT help me on this?
I am using XSL version 1.0.
I have declared a parameter in XSL file like:
<xsl:param name="HDISageHelpPath"/>
Now I am assigning the value to this parameter from an asp page . The value which I assign is "document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help". Now I want to assign this parameter to the <xsl for each> like
<xsl:for-each select="msxsl:node-set($HDISageHelpPath)" > (This does not work)
But it does not work. I checked the parameter value by debugging it as below
<debug tree="$HDISageHelpPath">
<xsl:copy-of select="$HDISageHelpPath"/>
</debug>
I'm able to print the value and it seems correct. In fact when I assign the static path ("document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help") by hard-coding it, it works
<xsl:for-each select="document('../ChannelData/Sage/help/ic/xml/HDI.xml')/HelpFiles/Help"> (This works)
Can anyone please let me know why assigning the parameter to xsl:for-each does not work?
Note: I have referred the site "http://www.dpawson.co.uk/xsl/sect2/N1553.html"
You can't easily evaluate dynamic strings as XPath expressions in XSLT 1.0. They must be hard-coded, normally.
There's EXSLT's dyn:evaluate(), but I doubt you can use that with the MXSML processor.
As an alternative approach, you could either try passing the file path only:
<xsl:param name="HDISageHelpFilePath"/>
<!-- ... -->
<xsl:for-each select="document($HDISageHelpFilePath)/HelpFiles/Help">
</xsl:for-each>
or making placeholder, replacing it with search-and-replace before you load the actual XSL code into the processor (as a string). This is a bit messy and error-prone, but it could give you the possibility to use an actual dynamic XPath expression.
<xsl:for-each select="%HELP_FILE_XPATH%">
</xsl:for-each>
Load the file as text, replace %HELP_FILE_XPATH% with your actual XPath, feed it to the processor. If it loads, you are fine, if it doesn't, your input XPath was malformed.