how to match the case sensitive characters in elements - xslt-2.0

I want to match the two or more case-sensitive characters in element of familName in author element. If found the ERROR message should be shown otherwise the familyName content will shown. The below code is not viewed in browser. Please check. I have used the xsl version is 2.0.
XML CODE
<author><familyName>CH</familyName> <givenNames>JC</givenNames></author>
XSLT CODE
<xsl:for-each select="author">
<xsl:choose>
<xsl:when test="matches(familyName,'([A-Z]){{1,}}')"><xsl:text>ERROR</xsl:text></xsl:when>
<xsl:otherwise>
<xsl:value-of select="familyName"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>

Its working for me. You can write a specific template for showing error:
<xsl:template match="familyName[matches(.,'^[A-Z][A-Z]+')]">
<xsl:text>ERROR</xsl:text>
</xsl:template>

Related

Conditional Formatting XSL

I'm trying to make <sup> elements superscripted when I encounter them. I'm iterating over a large file which I can include if required, basically <xml><article><body><p><em></em><sup></sup></p></body></article></xml>
I'm receiving:
Error reported by XML parser: The element type "fo:inline" must be terminated by
the matching end-tag "</fo:inline>"
when trying to use the below to raise the superscripts:
<xsl:for-each select="*">
<fo:block>
<xsl:if test="name() = 'sup'">
<fo:inline vertical-align='super' baseline-shift='4pt'>
</xsl:if>
<xsl:apply-templates select="." mode="xhtml"/>
<xsl:if test="name() = 'sup'">
</fo:inline>
</xsl:if>
</fo:block>
</xsl:for-each>
How can I correct this so the vertical-align='super' is only for sup elements; and is there a better approach to this? I plan to do the same for ems later.
My code which I use currently but puts everything out as plain text is:
<xsl:for-each select="*">
<fo:block><xsl:apply-templates select="." mode="xhtml"/></fo:block>
</xsl:for-each>
If you want to transform <sup></sup> to <fo:inline vertical-align='super' baseline-shift='4pt'></fo:inline> then the usual way with XSLT is to set up a template
<xsl:template match="sup">
<fo:inline vertical-align='super' baseline-shift='4pt'>
<xsl:apply-templates/>
</fo:inline>
</xsl:template>
I am not sure whether you want to do that in general or for a particular mode (in that case add mode="mode-name" on the xsl:template and mode="#current" on the xsl:apply-templates).

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

apply templates select substring-after

I've the below XML line.
<toc-title>1. <content-style>Short title</content-style></toc-title>
here i wanted to apply templates on substring-after 1.
I tried the below XSLT.
<xsl:template match="toc-title/text()" mode="x">
<xsl:analyze-string select="substring-after(.,' ')" regex="([a-z]+)">
<xsl:matching-substring>
<xsl:apply-templates select="regex-group(1)" mode="x"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="."/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="text()" mode="x">
</xsl:template>
but when i run this it throws the below error.
XSLT 2.0 Debugging Error: Error: file:///C:/Users/u0138039/Desktop/FLPHK_CHAP.xsl:239: Not a node item - item has type xs:string with value 'title' - Details: - XTTE0520: The result of evaluating the 'select' attribute of the <xsl:apply-templates> instruction may only contain nodes
I'm, unable to how to do this. please help me on fixing it.
Thanks

pattern not matching though declared

I've the below XML.
<root>
<para>
<label>5.</label> In essence, the Court in <star.page>19</star.page>
</para>
<para>
<label><star.page>21</star.page> 13.</label> Frankly, I cannot see how
one can escape
</para>
</root>
and using the below XSLT.
<xsl:template match="para">
<xsl:apply-templates select="./node()[1][self::star.page]|./label/node()[1][self::star.page]" mode="first"/>
</xsl:template>
<xsl:template match="star.page" mode="first">
<xsl:if test="preceding::star.page">
<xsl:processing-instruction name="pb">
<xsl:text>label='</xsl:text>
<xsl:value-of select="."/>
<xsl:text>'</xsl:text>
<xsl:text>?</xsl:text>
</xsl:processing-instruction>
<a name="{concat('pg_',.)}"/>
</xsl:if>
</xsl:template>
here when i try to run this code, the first para star.page is getting caught, but the second star.page, i.e. <para><label><star.page>21</star.page> 13.</label>... is not getting caught. please let me know where am i going wrong. here i'm taking [1], since i want to catch the first occurance.
Thanks
I just tried your code on xmlplayground, both the star.page elements reach the template but the if clause is preventing the first from reaching the output.

Use a dynamic match in XSLT

I have an external document with a list of multiple Xpath like this:
<EncrypRqField>
<EncrypFieldRqXPath01>xpath1</EncrypFieldRqXPath01>
<EncrypFieldRqXPath02>xpath2</EncrypFieldRqXPath02>
</EncrypRqField>
I use this document to obtain the Xpath of the nodes I want to be modified.
The input XML is:
<Employees>
<Employee>
<id>1</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>32</age>
<department>xyz</department>
</Employee>
</Employees>
I want to obtain something like this:
<Employees>
<Employee>
<id>XXX</id>
<firstname>xyz</firstname>
<lastname>abc</lastname>
<age>XXX</age>
<department>xyz</department>
</Employee>
</Employees>
The XXX values are the result of a data encryption, I want to dynamically obtain the Xpath from the document and change the value of its node.
Thanks.
I'm not sure if something like this is possible in XSL 2.0. May be in 3.0 there should be some function evaluate() but I don't know any details.
But I tried some workaround and it seems to be functional. Of course it is not perfect and has many limitations in this form (e.g. you need to specify absolute path, you cannot use more complex XPath like //, [], etc.) so consider it just as an idea. But it could be the way in some easier cases.
It is based on comparing of two string instead of evaluation string as XPath.
Simplified xml with xpaths to encrypt (I ommit the number for simplicity).
<?xml version="1.0" encoding="UTF-8"?>
<EncrypRqField>
<EncrypFieldRqXPath>/Employees/Employee/id</EncrypFieldRqXPath>
<EncrypFieldRqXPath>/Employees/Employee/age</EncrypFieldRqXPath>
</EncrypRqField>
And my transformation
<xsl:template match="element()">
<xsl:variable name="pathToElement">
<xsl:call-template name="getPath">
<xsl:with-param name="element" select="." />
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$xpaths/EncrypFieldRqXPath[text() = $pathToElement]">
<!-- If exists element with exacty same value as constructed "XPath", ten "encrypt" the content of element -->
<xsl:copy>
<xsl:text>XXX</xsl:text>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates />
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- This template will "construct" the XPath for element under investigation. -->
<!-- There might be an easier way (e.g. some build-in function), but it is actually out of my skill. -->
<xsl:template name="getPath">
<xsl:param name="element" />
<xsl:choose>
<xsl:when test="$element/parent::node()">
<xsl:call-template name="getPath">
<xsl:with-param name="element" select="$element/parent::node()" />
</xsl:call-template>
<xsl:text>/</xsl:text>
<xsl:value-of select="$element/name()" />
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>

Resources