How to use contains() with a set of strings in XSLT - 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.

Related

Modify date as per matching a attribute name value in 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'">

xquery/xslt for selecting a value based on a specific value

I am stuck on an xquery for below request. I want to select the address based on the type value.
i.e. if type is "StreetAddress" then pick freeFormat "Maguire"
<associatedAddress>
<address xmlns="http://services.oracle.com/v1.0/Common">
<type xmlns="http://services.oracle.com/v1.0/Common">StreetAddress</type>
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">Maguire</freeFormat>
</address>
<address>
<type xmlns="http://services.oracle.com/v1.0/Common">CityAddress</type>
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">SanFransisco</freeFormat>
</address>
</associatedAddress>
I tried in couple of ways:
if associatedAddress/address/type="StreetAddress"
then.., But this one gives me only the first address
I also tried to use a for loop and then use if case inside it but even that gave me just the first address
Please let me know any other options. Thanks
The XPath for fetching value Maguire using the value in <type> is
<xsl:value-of select="associatedAddress/address[type='StreetAddress']/freeFormat" />
You need to take care of the namespace associated with the different elements in your input XML when fetching the value. If the namespace is not properly handled in the XSL, the value will not be extracted.
Since you asked for xQuery given your input xml, this:
declare namespace common="http://services.oracle.com/v1.0/Common";
for $n in associatedAddress//common:type[. = "StreetAddress"]
return
$n/../common:freeFormat
produces the desired result
<freeFormat xmlns="http://services.oracle.com/v1.0/Common">Maguire</freeFormat>
The trick is that in your input xml associatedAddress is not in the same namespace as the other elements, so you need to adjust your query to take this into account.
You mention an if clause, but its is not clear what the desired output is from your question. You can use the below as a template to modify results.
declare namespace common="http://services.oracle.com/v1.0/Common";
for $n in associatedAddress//common:type
return
switch($n)
case "StreetAddress" return $n/../common:freeFormat
case "CityAddress" return "something else"
default
return ()

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>

how to use two conditions in select conditions in xslt when using Apply template

<xsl:apply-templates mode="block2sequence" select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] and NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
why cant i use two conditions in above select condition, can any one suggest me
<xsl:apply-templates mode="block2"
select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] "/>
why cant i use two conditions in above select condition
I guess this is to mean, "why can't the two conditions be specified in the same predicate?"
The answer is that the expression:
NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId and position() = 1]
isn't equivalent at all to the 1st expression above.
The first expression selects the first Table child of NewDataSet such that the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case we don't know which child (at which position) of NewDataSet will be selected -- any child that happens to be the first with the specified properties, will be selected.
On the other side, the latter expression selects the first Table child of NewDataSet only if the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case, if anything is selected, it would be the first Table child.
If you want an equivalent expression to the first one, that has only one predicate, one such expression is:
NewDataSet/Table
[CTD_CTD_PKG_ID =$PackageId
and
not(preceding-sibling::Table[CTD_CTD_PKG_ID =$PackageId ])
]
Update: The OP has published a code snippet:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1]
and
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
This code will cause an error thrown at compile time by the XSLT processor.
The value of the select attribute is a boolean (expr1 and expr2), however templates in XSLT 1.0 and XSLT 2.0 can only be applied on nodes. A boolean isn't a node -- hence the error.
Solution:
My first guess is that you want templates to be applied on both nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][1]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][1]"/>
My second guess is that you want templates applied only on the first of the two nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"(NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType]
)
[1]
"/>
Notes:
Please, learn how to ask a question -- provide all relevant data and explain -- in the question, not in subsequent comments.
Did you know that [1] is equivalent to [position()=1] and is shorter?
You can use two conditions and your expression looks perfectly correct. If it is failing with an error, please tell us the error. If it is not selecting what you want, then (a) show us your source document, and (b) tell us what you want to be selected.
(You know, your question gives so little information, you don't give the impression that you really want an answer.)

XSL: Combining grouping and call-template

I've read with interest the techniques available on the web to extract a unique list of items from a XML file containing duplicates using XSL.
These range into 2 categories:
1) The Muenchian method (example: http://www.jenitennison.com/xslt/grouping/)
2) Or the previous-sibling look-up
These both rely on an XPath expression to select the data to group by.
However, in the XML file that I'm trying to work out, the data is not present "natively" in the XML file. I am using a xsl:template to compute some aggregated data from my elements. And I would like to group based on the aggregated data.
For example I have:
<filmsreview>
<record><data name='movie'>Star Wars</data><data name='ratings'>John:Good, Mary:Good</data></record>
<record><data name='movie'>Indiana Jones</data><data name='ratings'>John:Good, Mary:Bad, Helen:Average</data></record>
<record><data name='movie'>Titanic</data><data name='ratings'>John:Bad, Helen:Good</data></record>
</filmsreview>
I know that the structuration of data is not perfect and that by creating sub-elements I could do something easier, but I cannot change the data source easily, so let's take this as a challenge.
And I would like to build a recap where I have John's distinct ratings:
John's ratings:
Good
Bad
I have a xsl:template that can take a record element and return John's rating for this record:
Example:
<xsl:template name="get_rating">
<xsl:param name="reviewer" />
<!-- I use some string manipulation, and xsl:value-of to return the review for John-->
</xsl:template>
I can just call it under a xsl:for-each to get the exhaustive list of John's review. But I cannot combine this call with any of the methods to get unique values.
Do I have to use an intermediary XSL to convert my XML file to a more structured way? Or can I do in a single step?
Many thanks
Gerard
Hmm... This should be possible using xslt variables and the nodeset method, perhaps something like this:
<xsl:variable name="_johnsRatings">
<xsl:apply-templates select="/" mode="johnsRatings" />
</xsl:variable>
<xsl:variable name="johnsRatings" select="msxsl:node-set($_johnsRatings)" />
<xsl:template match="/" mode="johnsRatings">
<Ratings>
<xsl:for-each select="/filmsReview/record/data[#name='ratings']">
<Rating><xsl:call-template name="get_rating" /></Rating>
</xsl:for-each>
</Ratings>
</xsl:template>
At this point, it should be possible to query the $johnsRatings variable using standard XPath queries, and you can use either of the two methods you mentioned above to retrieve unique values from it...
Hope that helps
EDIT:
I don't know what XSLT engine you are using, I assumed you have access to the msxsl:node-set() function. However, most XSLT processors have similar methods, so you might have to search around for an equivalent method in your processor