XSLT 1.0 Regular Expression in for loop - regex

So I have xml like
<depot id="D7">
<address>
<number>6000</number>
<street>Yonge</street>
<city>Toronto</city>
<province>ON</province>
<postal_code>M2M 2E4</postal_code>
</address>
</depot>
I have hundreds of there depot in xml
now in my xsl, I have defined a variable called 'locale' that stores a postal code like "M1C".
After this I want to select only those depot where the postal_code is like 'locale'. In other words, If I specify locale to be "M1C", then I should get all the depot whose postal_code contains "M1C", so depot with "M1C A18", "M1C B2C", etc all should be in the result.
Currently I have the line below
< xsl:for-each select="depot[address[postal_code=$locale]]">
which gives me only depot with exact postal code match and not the ones with "M1C A18", "M1C B2C", etc. I want to use something like
<xsl:for-each select="depot[address[postal_code=*$locale*]]">
with wildcards but it does not works. Suggestions?

Use:
depot[starts-with(address/postal_code, $locale)]
Here we assume that any depot has a single address/postal_code descendent and that no possible value of $locale is a prefix of any other possible value of $locale.
If, for example, the second assumption isn't true, then use:
depot[starts-with(address/postal_code, concat($locale, ' '))]
True regular expression capabilities are available in XPath 2.0 (such as the matches() function), but they aren't necessary for a simple problem as this one.

Use this:
<xsl:for-each select="depot[contains(address/postal_code,$locale)]" />
to match only depot elements that contain the fragment defined in $locale.

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'">

Refer to specific cell in xslt import/export filter for Calc

I am using xslt filter for importing/exporting data from Calc worksheet. Is it possible to refer to a specific cell address ? For example, if we want to export data from cell B2, how do we refer to this cell address in export xslt ?
Without knowing much about Openoffice or their xslt filter function, I can tell you that you're probably going to need a fairly simple XPath to reference a specific Cell's data - I doubt it would be as simple as calling getCell('B2') unless they have provided you with some custom xslt functions (I'm assuming they've put you in a raw XSLT environment).
Anyway, I think this question may be more about XSLT and xpath, than it is about openoffice. With that in mind, I'm going to fashion my own sample xml and examples and hopefully that will be enough to get you started.
For an input xml that looks something like this:
<ooo_calc_export>
<ooo_sheet num="1" name="sheet1">
<ooo_row num="2">
<fisrtCell>Oh</firstCell>
<secondCell>Hai</secondCell>
<thirdCell>There</thirdCell>
</ooo_row>
<ooo_row num="3">
<fisrtCell>Oh</firstCell>
<secondCell>Hello</secondCell>
<thirdCell>Back!</thirdCell>
</ooo_row>
</ooo_sheet>
</ooo_calc_export>
An absolute XPath to access cell B2's data would look like this ooo_calc_export/ooo_sheet/ooo_row[#num='2']/secondCell/text()
But the above is an absolute path and in XSLT, we would often author relative xpaths as we are in the midst of processing a document. Imagine you're in a template which matches on the ooo_calc_export node and you wanted to store Cell B2's data in a variable for later use. Consider this example:
<xsl:template match="/ooo_calc_export">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2" select="ooo_sheet/ooo_row[#num='2']/secondCell/text()" />
</xsl:template>
Now lets imagine you wanted a template to match on the cell B2 node itself:
<xsl:template match="ooo_row[#num='2']/secondCell">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2_text" select="text()" />
</xsl:template>
This is a good tutorial on XSLT to get you started. Also, the W3 Schools references on XPath and XSLT aren't the worst.

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.

Idiom for templating similar to other templating engines like Velocity?

I have an XSLT(2.0) file; which takes an input XML data file and creates DDL/SQL Statements.
It works just fine. But it is a bit difficult to maintain, as it contains a lot of formatting information in 'concat' statements like this:
<xsl:value-of select="concat('CREATE USER ',$username,' IDENTIFIED BY ',$password,';',$nl)"/>
What I would prefer to do would be to encode my SQL Statements in a manner like this instead:
<some-enclosing-elements>[...]CREATE USER <username/>, identified by <password/>; [literally a newline here][...]</some-enclosing-elements>
I would perhaps keep this format above in the XML data file itself in a 'lookup' table at the top of the either the XSLT or the data document iself (I can't work out which yet).
Is there a standard idiom that would allow this kind of templating ?
Any ideas ?
By the way; the data document contains many different users to create of course
The AVT approach is just a little bit too devious for my taste. I tend to rely on the implicit concatenation done (in 2.0) by xsl:value-of:
<xsl:value-of select="'CREATE USER', $username, 'identified by', $password"/>
Another approach which I have used in applications where this kind of text templating is significant is to essentially write my own templating engine within XSLT; have a "message file" containing message templates in the form
<message nr="1">CREATE USER <p:user/> IDENTIFIED BY <p:password/></message>
and then write template rules to expand the messages by substituting the parameters.
#xiaoyi is right, showing the main alternative to using concat(). However that's even more notation-heavy than the concat(), since you have to keep repeating <xsl:value-of select="..." />.
A nice alternative would be to use attribute value templates (AVTs):
[...]CREATE USER {username}, identified by {password};
[...]
But ATVs are only available for (certain) attributes, not for text nodes (directly). How do you use them for this purpose?
One way in XSLT 2.0 would be to use an AVT to create a new literal result element with an attribute; specify the value of that attribute using an AVT; and then select the value of the new attribute:
<xsl:variable name="query">
<dummy val="[...]CREATE USER {username}, identified by {password};
[...]" />
</xsl:variable>
<xsl:value-of select="$query//#val" />
Yes that's some significant overhead per formatted string, but there's very little overhead per field within the string. You could do several strings together like this:
<xsl:variable name="queries">
<q val="[...]CREATE USER {username}, identified by {password};
[...]" />
<q val="[...]CREATE TABLE {tablename}, blah blah;
[...]" />
</xsl:variable>
<xsl:value-of select="$queries/q[1]/#val" />
<xsl:value-of select="$queries/q[2]/#val" />
You could use position indices as above, or use an id attribute to identify each string.
I have not seen this method advocated elsewhere, so I'd be curious to hear what others think about it.
Never mind, except...
Given the simpler approach shown by Michael Kay's answer, I don't think there's any point in doing it this way. I guess that explains why others haven't advocated this method. :-)
The only situation I can think of where this approach might still be of use is if you can't use XSLT 2.0, but you do have access to the nodeset() extension function (e.g. in IE or .NET environment). In that case you would need to wrap nodeset( ) around $queries wherever you used it in an XPath expression before /.

XSLT to call secondary XML based on first XML element/attribute

love the stuff - newbie Æthelred here
I have a XSLT 1.0 file pulling in a secondary XML (to a variable) to build a table
<xsl:variable name="table_values" select="document('./table_variants/external_table.xml')/xml/channel_1"/>
I then get the values i need from the variable, eg:
<xsl:value-of select="$table_values/monkey/tennis/#medals"/>
<xsl:value-of select="$table_values/monkey/tennis/#bananas"/>
What i want to do is have the first XML trigger/steer where to look for the table data.
I hoped i could, within the triggered XML, state the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/>
but apparently i cannot create a xpath on the fly like that
Please - What can i do?
What i want to do is have the first XML trigger/steer where to look
for the table data. I hoped i could, within the triggered XML, state
the last part of the xpath - the 'channel_1' or 'channel_2',
<xsl:value-of select="xml/external_table_channel_to_use"/> but
apparently i cannot create a xpath on the fly like that
Please - What can i do?
This can easily be done just extending the code that you already have.
Change this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')/xml/channel_1"/>
to this:
<xsl:variable name="table_values" select=
"document('./table_variants/external_table.xml')
/xml/*[name() = $channelName"/>
Needless to say, the variable (or global, external param) $channelName should have a value that is the (string) name of the element you want to use in the last location step of the XPath expression.