XSLT 2.0: Template match only if child element's value matches regex - xslt-2.0

Given this XML
<root>
<card>
<k>заада</k>
<blockquote>заада I</blockquote>
<blockquote>ир.</blockquote>
<blockquote>(или жүрөк заада)</blockquote>
</card>
...
</root>
I am trying to get only those Cards where any child blockquote element's value matches a regex. The XSL below isn't working, and I know why it doesn't work, I just can't figure out how to make it work.
<xsl:template match="/">
<xsl:apply-templates select="//card[matches(blockquote, '^\w+-? I:?$')]"/>
</xsl:template>
<xsl:template match="card">
<xsl:copy-of select="." />
</xsl:template>

Related

How to filter nodes based on certain condition of the child node text

I have an XML file as shown below.
<COLLECTION>
<ChangedParts>
<Part>
<number>123456</number>
<DefaultUnit>each</DefaultUnit>
<FgOrComponent>FG</FgOrComponent>
<MasterPackUom/>
<CartonUom/>
</Part>
<Part>
<number>456789</number>
<DefaultUnit>each</DefaultUnit>
<FgOrComponent>COMPONENT</FgOrComponent>
<MasterPackUom/>
<CartonUom/>
</Part>
</ChangedParts>
</COLLECTION>
I am trying to use XSLT to transform the file. The file contains Part elements with FgOrComponent and some other elements as its child nodes. FgOrComponent has either FG or COMPONENT has it value. I need to select only the Part element with FG as its value for the FgOrComponent element and modify some other elements like etc in the selected part. The expected output is as shown below.
<COLLECTION>
<ChangedParts>
<Part>
<name>123456</name>
<DefaultUnit>ea</DefaultUnit>
<FgOrComponent>FG</FgOrComponent>
<MasterPackUom>mp</MasterPackUom>
<CartonUom>ca</CartonUom>
</Part>
<Part>
<number>456789</number>
<DefaultUnit>each</DefaultUnit>
<FgOrComponent>COMPONENT</FgOrComponent>
<MasterPackUom/>
<CartonUom/>
</Part>
</ChangedParts>
</COLLECTION>
I am using the following XSLT file to do the transformation without any success. Any help would be appreciated.
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*/*/Part[(FgOrComponent = 'FG')]/*">
<xsl:choose>
<xsl:when test="MasterPackUom/text() = ''">
<MasterPackUom>mp</MasterPackUom>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
The test clause "MasterPackUom/text() = '' is never reached.
If the element is empty then it doesn't have any text() node children, just check MasterPackUom = ''.
But as you have the identity transformation set up as a base transformation, please simply write templates for the relevant changes e.g.
<xsl:template match="Part[FgOrComponent = 'FG']/MasterPackUom[. = '']">
<xsl:copy>mp</xsl:copy>
</xsl:template>
instead of doing that odd xsl:choose.

Transform only the last element of XML and copy the rest in XSLT

I have an XML like below -
<root>
<row>
<col1>16</col1>
<col2>466</col2>
<col3>144922</col3>
<col4>0</col4>
<col5>5668</col5>
<col6>475</col6>
</row>
</root>
The number of columns can vary inside the root element. It can also be up to col9. My requirement is to modify the last column and copy others as it is for an incoming XML.
I have something like this till now where I am assigning the value to used as the last element in a variable and then trying to call it when the last position is reached-
<?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:param name="line88.field2" />
<xsl:param name="rec16.col2" />
<xsl:variable name="col3">
<xsl:choose>
<xsl:when test="$rec16.col2 ='165'">
<xsl:value-of select="'Y'"/>
</xsl:when>
<xsl:when>
------
<xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"></xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="row[position() = last()]">
<col9>
<xsl:call-template name="AnotherTemplate">
<xsl:with-param name="inputData">
<xsl:value-of select="$col3" />
</xsl:with-param>
</xsl:call-template>
</col9>
</xsl:template>
<xsl:template name="AnotherTemplate">
<xsl:param name="inputData"></xsl:param>
<xsl:value-of select="$inputData" />
</xsl:template>
</xsl:stylesheet>
But this is not working for me. Just giving me one column with the modified value.Please help.
The desired outcome should be as below where the last column has the value from the variable.
<root>
<row>
<col1>16</col1>
<col2>466</col2>
<col3>144922</col3>
<col4>0</col4>
<col5>5668</col5>
<col6>Y</col6>
</row>
</root>
Without knowing your whole XSLT code. You can use this row template:
<xsl:template match="row/*[starts-with(local-name(),'col') and position() = last()]">
<xsl:element name="{concat('col',position() div 2)}">
<xsl:call-template name="AnotherTemplate">
<xsl:with-param name="inputData">
<xsl:value-of select="$col3" />
</xsl:with-param>
</xsl:call-template>
</xsl:element>
</xsl:template>
It replaces the last col? element by the given value (the result of the xsl:call-template code).

How to Create an Element from Two Surrounding Elements?

I am stuck with an XML to XML transformation using XSLT 2.0 where I need to transform this:
<p>some mixed content <x h="">START:attr="value"</x> more mixed content <x h="">END</x> other mixed content</p>
To this:
<p>some mixed content <ph attr="value"> more mixed content </ph> other mixed content</p>
So basically I'd like to replace <x h="">START:attr="value"</x> with <ph attr="value">
and <x h="">END</x> with </ph> and process the rest as usual.
Does anyone know if that's possible?
My main issue is that I cannot figure out how to find the element with value END and then tell the XSLT processor (I use saxon) to process the content between the first occurence of and the second occurence of and finally write the end element . I am familiar with how to create an element (including attributes).
I have a specific template to match the start element START:attr="value". Since the XML document I process contains many other elements I'd prefer a recursive solution, so continue the processing of the found content between START and END by using other existing templates.
Sample XML
(note that I don't know in advance if the parent will be a p element)
<p> my sample text <b>mixed</b> more
<x h="">START:attr="value"</x>
This is mixed content <i>REALLY</i>, process it normally
<x h="">END</x>
</p>
My Stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="x[#h][starts-with(., 'START:')]">
<ph>
<xsl:for-each-group select="../*" group-starting-with="x[#h][. = 'START:']">
<xsl:for-each-group select="current-group()" group-ending-with="x[#h][. = 'END']">
<xsl:apply-templates select="#*|node()|text()"/>
</xsl:for-each-group>
</xsl:for-each-group>
</ph>
</xsl:template>
<xsl:template match="x[#h][starts-with(., 'END')]"/>
<xsl:template match="node()|#*">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<p> my sample text <b>mixed</b> more
<ph>mixed</ph>
This is mixed content <i>REALLY</i>, process it normally
</p>
I cannot figure out how to put the complete content between START and END within the tags. Any ideas?
I would match on the parent containing those markers and use a nested for-each-group, of course all based on the identity transformation template as the base processing:
<xsl:template match="p[x[#h][starts-with(., 'START:')]]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:for-each-group select="node()" group-starting-with="x[#h][starts-with(., 'START:')]">
<xsl:choose>
<xsl:when test="self::x[#h][starts-with(., 'START:')]">
<xsl:variable name="value" select="replace(., '(START:attr=")([^"]*)"', '$2')"/>
<xsl:for-each-group select="current-group()[position() gt 1]" group-ending-with="x[#h][. = 'END']">
<xsl:choose>
<xsl:when test="current-group()[last()][self::x[#h][. = 'END']]">
<ph attr="{$value}">
<xsl:apply-templates select="current-group()[position() ne last()]"/>
</ph>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
XSLT 3 example at https://xsltfiddle.liberty-development.net/pPJ8LV4, for XSLT 2 you need to replace the used xsl:mode declaration with <xsl:template match="#* | node()"><xsl:copy><xsl:apply-templates select="#* | node()"/></xsl:copy></xsl:template>.
As Saxon also supports XQuery using tumbling window where you can check both the start and the end condition together might be a bit more concise (although in XQuery you have to do extra work to make sure you pass the stuff not being wrapped through as the windowing normally filters out items for which the conditions not hold):
p ! <p>
{
for tumbling window $group in node()
start $s
when $s[self::x[#h][starts-with(., 'START:')]] or true()
end $e
when $e[self::x[#h][. = 'END']] and $s[self::x[#h][starts-with(., 'START:')]] or not($s[self::x[#h][starts-with(., 'START:')]])
return
if ($s[self::x[#h][starts-with(., 'START:')]])
then
<ph value="{replace($group[1], '(START:attr=")([^"]*)"', '$2')}">
{
tail($group)[not(position() = last())]
}
</ph>
else $group
}
</p>
https://xqueryfiddle.liberty-development.net/948Fn5s/2

XSLT set directory where result document ends up

The XSLT below creates result-documents as desired, with one exception: the result document ends up in the directory where the stylesheet was invoked from. I want the result document to be where it was found (i.e. overwrite itself with the transform version).
How can I do that?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0" xpath-default-namespace="http://www.w3.org/1999/xhtml">
<xsl:template match="/">
<xsl:for-each select="collection(iri-to-uri('file:///home/paul/Text/?select=*.xhtml'))">
<xsl:variable name="filename">
<xsl:value-of select="tokenize(document-uri(.), '/')[last()]"/>
</xsl:variable>
<xsl:result-document indent="yes" method="xml" href="{$filename}">
<xsl:apply-templates/>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- transform templates removed -->
</xsl:stylesheet>
Try just using href="{document-uri(.)}" to use the full uri as the target rather than doing the tokenize to pull out the last segment.

How do I merge and concatenate the data from each row in two separate source files?

I have two source files which I need to combine on a row by row basis. I am happy reading the files into a variable and I am happy with the logic but the syntax has me stumped. For each row in file 1 I need to loop round each row in file 2 and output the two variables concatenated together:
File 1:
<rows>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
</rows>
File 2:
<rows>
<row>a</row>
<row>b</row>
</rows>
Required output:
<rows>
<row>1/a</row>
<row>1/b</row>
<row>2/a</row>
<row>2/b</row>
<row>3/a</row>
<row>3/b</row>
<row>4/a</row>
<row>4/b</row>
<rows>
My (poor) attempt at getting the XSLT to work:
<rows>
<xsl:apply-templates select="document('file1.xml')/rows/row" />
</rows>
<xsl:template match="row">
<xsl:apply-templates select="document('file2.xml')/rows/row" />
</xsl:template>
<xsl:template match="row">
<row><xsl:value-of select="???" />/<xsl:value-of select="???" /></row>
</xsl:template>
(These files are simplified versions of what I actually have)
How do I make one template match one 'row' value and the other match another (both source files use the same structure). And how do I set those '???' values?
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vDoc2">
<rows>
<row>a</row>
<row>b</row>
</rows>
</xsl:variable>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<rows>
<xsl:apply-templates/>
</rows>
</xsl:template>
<xsl:template match="row">
<xsl:apply-templates select="$vDoc2/*/row" mode="doc2">
<xsl:with-param name="pValue" select="."/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row" mode="doc2">
<xsl:param name="pValue" />
<row><xsl:sequence select="concat($pValue, '/', .)"/></row>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided first XML document:
<rows>
<row>1</row>
<row>2</row>
<row>3</row>
<row>4</row>
</rows>
the wanted, correct result is produced:
<rows>
<row>1/a</row>
<row>1/b</row>
<row>2/a</row>
<row>2/b</row>
<row>3/a</row>
<row>3/b</row>
<row>4/a</row>
<row>4/b</row>
</rows>

Resources