Merging and inheriting parameters - saxon

Using xslt version 3.0 (saxon):
I have something like the following
<root>
<template ID='1'>
<params>
<a>1</a>
<b>1</b>
</params>
</template>
<document1 templateID='1'>
<params>
<b>4</b>
<c>5</c>
</params>
</document1>
</root>
Basicly I need to convert into something like
<root>
<document1 templateID='1'>
<params>
<a>1</a>
<b>4</b>
<c>5</c>
</params>
</document1>
</root>
In the example parameter a is inherited from the template while parameter b is overwritten by the document itself and parameter c is not known or set in the template. It is akin to inheritance or how css work. I hope you get the idea. Before starting the task I thought this should not be too difficult (and still hoping Im just overlooking something).
I have tried something with concat'ing the two nodeset (using nodeset1 , nodeset2 to preserve the order) and using a preceding-sibling name based 'select'/'filtering' - but this strategy seems not to work as it seems they are not actual siblings. Could this be done with a clever group-by ? Can it be done at all ? (I think it can)
I am using xslt version 3.0 (saxon)

I think you want to group or merge, merging in XSLT 3 would be
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="template-by-id" match="template" use="#ID"/>
<xsl:template match="template"/>
<xsl:template match="*[#templateID]/params">
<xsl:copy>
<xsl:merge>
<xsl:merge-source name="template" select="key('template-by-id', ../#templateID)/params/*">
<xsl:merge-key select="string(node-name())"/>
</xsl:merge-source>
<xsl:merge-source name="doc" select="*">
<xsl:merge-key select="string(node-name())"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy-of select="(current-merge-group('doc'), current-merge-group('template'))[1]"/>
</xsl:merge-action>
</xsl:merge>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyH9rN8/
grouping would be
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output indent="yes"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="template-by-id" match="template" use="#ID"/>
<xsl:template match="template"/>
<xsl:template match="*[#templateID]/params">
<xsl:copy>
<xsl:for-each-group select="key('template-by-id', ../#templateID)/params/*, *" group-by="node-name()">
<xsl:copy-of select="head((current-group()[2], .))"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/jyH9rN8/1
I think, as xsl:merge requires input to be sorted on any merge key or to sort the input first, the grouping above is easier and more reliable, unless your params child elements are really named with sorted letters or words from the alphabet.

Related

Looking for saxon:evaluate() example code

I have a transform.xsl file with will process a input.xml. But there is also an additional config.xml file which will define additional clauses. For e.g. this is the content of the config.xml.
<Location >
<DisplayName>
<Attribute1>ABC</Attribute1>
<Attribute2>XYZ</Attribute2>
<action>concat($Attribute1,$Attribute2)</action>
</DisplayName>
</Location >
So when transform.xsl will encounter the DisplayName variable within the input.xml, then it will form the value with the RESULT of the action expression defined in the config.xml file. transform.xml will call the config.xml just to get the result. (The action can be modified by the end user and hence these are placed outside the xsl file, within the config.xml).
We are using saxon xml processor version 9 and xslt 2.0. So we need to use saxon:evaluate(). I tried to find more examples of saxon:evaluate(), but couldn't find it more. Can anyone show me some examples of how to use it?
Thanks in advance.
***** This is an edited query to highlight the need of saxon:evaluate *****
Here is an example to use an XSLT 3 processor supporting xsl:evaluate (https://www.w3.org/TR/xslt-30/#dynamic-xpath) (i.e. Saxon 9.8 or later with the commercial PE or EE editions or Altova 2017 or later) to process your "config" file:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="config-url" as="xs:string">test2018121301.xml</xsl:param>
<xsl:param name="config-doc" select="doc($config-url)"/>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="element" match="*" use="node-name()"/>
<xsl:function name="mf:config-evaluation" as="item()*">
<xsl:param name="config-doc" as="document-node()"/>
<xsl:param name="element-name" as="xs:QName"/>
<xsl:variable name="display" select="key('element', $element-name, $config-doc)/DisplayName"/>
<xsl:evaluate xpath="$display/regex" with-params="map:merge($display!(* except regex)!map { QName('', local-name()) : string() })"/>
</xsl:function>
<xsl:template match="*[key('element', node-name(), $config-doc)]">
<xsl:copy>
<xsl:value-of select="mf:config-evaluation($config-doc, node-name()), ."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
So with a config.xml
<Location >
<DisplayName>
<Attribute1>ABC</Attribute1>
<Attribute2>XYZ</Attribute2>
<regex>concat($Attribute1,$Attribute2)</regex>
</DisplayName>
</Location >
this would transform an input sample with e.g.
<Root>
<Items>
<Item>
<Data>data 1</Data>
<Location>location 1</Location>
</Item>
<Item>
<Data>data 2</Data>
<Location>location 2</Location>
</Item>
</Items>
</Root>
into
<Root>
<Items>
<Item>
<Data>data 1</Data>
<Location>ABCXYZ location 1</Location>
</Item>
<Item>
<Data>data 2</Data>
<Location>ABCXYZ location 2</Location>
</Item>
</Items>
</Root>
That gives you a great flexibility to allow XPath expressions in the configuration files but as pointed out in https://www.w3.org/TR/xslt-30/#evaluate-effect, also is a security problem: "Stylesheet authors need to be aware of the security risks associated with the use of xsl:evaluate. The instruction should not be used to execute code from an untrusted source.".
As for using the saxon:evaluate function supported in older versions of Saxon not supporting the XSLT 3 xsl:evaluate instruction, a simple example is
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="#all"
version="2.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="example">
<xsl:copy>
<xsl:value-of select="saxon:evaluate(#expression, #foo, #bar)"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
which transforms the input
<root>
<example expression="concat($p1, $p2)" foo="This is " bar="an example."/>
<example expression="replace(., $p1, $p2)" foo="\p{L}" bar="X">This is example 2.</example>
</root>
into the result
<root>
<example>This is an example.</example>
<example>XXXX XX XXXXXXX 2.</example>
</root>
Try checking the xsl-attribute tag along with the xsl-value-of tag. If I get what you're asking for, you could probably read the config.xml using the transform.xsl (or a second xsl for an intermediate file) to set the text inside the regex tag to correspond to the value of an tag attribute within the xsl.
https://www.w3schools.com/xml/ref_xsl_el_attribute.asp
Also, check this tutorial for regex in XSLT 2, it may help:
https://www.xml.com/pub/a/2003/06/04/tr.html

Changing values that are the same from different nodes

I need to localize values within siblings that are the same. If they are the same I need to alter them.
I think I need to use following-sibling and preceding-sibling and group-by in some way. First group-by the value I am looking for so that I get the one's that are the same in the position after each other. Then using the sibling functions to find out if they are equal.
Sample:
<programs>
<event>
<start>2018-11-25T13:55:00</start>
</event>
<event>
<start>2018-11-27T17:00:00</start>
</event>
<event>
<start>2018-11-25T13:55:00</start>
</event>
<event>
<start>2018-11-25T13:55:00</start>
</event>
</programs>
Code:
<?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:template match="/">
<output>
<xsl:for-each select="/programs/event">
<xsl:variable name="starttime" select="./start"/>
<startOfProgram><xsl:value-of select="$starttime"/></startOfProgram>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Desired results:
<output>
<startOfProgram>2018-11-25T13:55:00</startOfProgram>
<startOfProgram>2018-11-25T13:56:00</startOfProgram>
<startOfProgram>2018-11-25T13:57:00</startOfProgram>
<startOfProgram>2018-11-27T17:00:00</startOfProgram>
</output>
I know this is a long shot so if anyone could point me in the right direction or help me with one part of the problem I'd be very grateful.
There is lots of other elements in the sample that I have taken out that is also carried though to the output. If it matters I can include a variety of them.
Ps. Note that the value could easily be 2018-11-25T18:30:00, which would then need to be 2018-11-25T18:30:00 and the consecutive 2018-11-25T18:31:00 if there are more of the same.
The result you have shown looks as if you want to group the values as xs:dateTime values and then simply add one minute to each item in the group depending on the position:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="programs">
<output>
<xsl:for-each-group select="event/start/xs:dateTime(.)" group-by=".">
<xsl:for-each select="current-group()">
<startOfProgram>{. + (position() - 1) * xs:dayTimeDuration('PT1M')}</startOfProgram>
</xsl:for-each>
</xsl:for-each-group>
</output>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPqsHUv/1 and the above is XSLT 3 but for an XSLT 2 processor I think you only need to change the text value template I have used to an xsl:value-of:
<startOfProgram><xsl:value-of select=". + (position() - 1) * xs:dayTimeDuration('PT1M')"/></startOfProgram>
See http://xsltransform.hikmatu.com/6qVRKvJ

tokenize with delimeter inside and outside of a string with xslt 2.0

I do have an input with randon values in parentheses,square brackets,Curly brackets and values outside brackets.Any type of bracket can occur in any randam position, where all are seperated by delimeter comma.
I have used <xsl:for-each select="tokenize(test,',')">
but as comma is present both inside and outside of brackets. It became impossible to achieve desired output. Please help me out
for example
INPUT
<test>{ST456,PT154},[GH456,JH768],(HJ789,KY456),GH789,PI345</test>
Desired OUTPUT
<test>{ST456,PT154}</test>
<test>[GH456,JH768]</test>
<test>(HJ789,KY456)</test>
<test>GH789</test>
<test>PI345</test>
You can use the xsl:analyze-string element in XSLT 2 or in XSLT 3 the same element or instead the analyze-string function, as in
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
expand-text="yes"
exclude-result-prefixes="xs fn"
version="3.0">
<xsl:param name="regex-pattern" as="xs:string" expand-text="no">\[([^\]]+)\]|\{([^\}]+)\}|\(([^)]+)\)|([^,]+)</xsl:param>
<xsl:output indent="yes"/>
<xsl:template match="test">
<xsl:apply-templates select="analyze-string(., $regex-pattern)//fn:group"/>
</xsl:template>
<xsl:template match="fn:group">
<test>{.}</test>
</xsl:template>
</xsl:stylesheet>
Online sample is at https://xsltfiddle.liberty-development.net/6qVRKw9, for XSLT 2 you would simply use an xsl:analyze-string element with the same pattern and then remove leading or trailing braces:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
expand-text="yes"
exclude-result-prefixes="xs"
version="2.0">
<xsl:param name="regex-pattern" as="xs:string" expand-text="no">\[([^\]]+)\]|\{([^\}]+)\}|\(([^)]+)\)|([^,]+)</xsl:param>
<xsl:output indent="yes"/>
<xsl:template match="test">
<xsl:analyze-string select="." regex="{$regex-pattern}">
<xsl:matching-substring>
<test>
<xsl:value-of select="replace(., '^[\[\{\(]|[\}\]\)]$', '')"/>
</test>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6qVRKw9/1

XSLT: output " without it being parsed

I am trying to achieve the following XML output:
<Foo bar=""" />
My XSLT file is as follows:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="quote">
<xsl:text>"</xsl:text>
</xsl:variable>
<Foo bar="{$quote}"/>
</xsl:template>
</xsl:stylesheet>
Unfortunately, this gives me the output:
<Foo bar="""/>
How do I alter my XSLT to output & quot; without it being parsed into either a " character or a & #34;?
Ian Roberts has already made the very good point that it doesn't actually matter. But if you really, really wanted to do this, then in XSLT 2.0 (but not XSLT 1.0) you could make use of a character map, like so:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" use-character-maps="quotes" />
<xsl:character-map name="quotes">
<xsl:output-character character=""" string="&quot;" />
</xsl:character-map>
<xsl:template match="/">
<xsl:variable name="quote">
<xsl:text>"</xsl:text>
</xsl:variable>
<Foo bar="{$quote}"/>
</xsl:template>
</xsl:stylesheet>

XSLT 2.0: Overriding nodes with grandchild nodes

I'm trying to find a way to replace a node with one that has the same name deeper down in the tree. For example, with the following input:
<root>
<foo>
<a>1</a>
<b>2</b>
<c>3</c>
<bar>
<a>100</a>
<c>5000</c>
</bar>
</foo>
</root>
I'd like to produce something like this:
<root>
<foo>
<a>100</a>
<b>2</b>
<c>5000</c>
</foo>
</root>
I need to be able to replace any number of nodes, and I'd also like to figure out the list dynamically, rather than spell out all the possibilities because there's a chance that things will change in the future. One other requirement is that order of the parent nodes must remain intact. (To be specific, my final output is going to be a CSV file so the columns need to line up with the headers.)
This is my first attempt at learning XSLT and I'm totally stumped on this one! Any help would be greatly appreciated. I'm using XSLT 2.0, BTW.
Thanks,
Mark
I apologize for the nasty SO bug which doesn't indent the formatted code!
They can't fix this for months...
This transformation:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[*]
[every $leaf in .//*[not(*)]
satisfies
name($leaf) = preceding::*/name()
]
"/>
<xsl:template match=
"*[not(*) and name() = following::*/name()]">
<xsl:sequence select=
"following::*[name() = name(current())][1]"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<foo>
<a>1</a>
<b>2</b>
<c>3</c>
<bar>
<a>100</a>
<c>5000</c>
</bar>
</foo>
</root>
produces the wanted, correct result:
<root>
<foo>
<a>100</a>
<b>2</b>
<c>5000</c>
</foo>
</root>

Resources