Changing values that are the same from different nodes - xslt-2.0

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

Related

How do I split and print values in xslt?

I want to create an xslt(version 2) where value can be split with delimeter ':' and print only first part of split and store the second part into a variable. The values have to be passed to a 'student' tag. Following are the values that are fetched from db
Adam:101
Brad:110
Chad:111
Expected output:
Adam
Brad
Chad
and values 101, 110 and 111 have to stored into a variable.
Please also provide a link where xslt2.0 tutorial is available in detail.
Simply you can use fn:tokenize() to achieve the output:
Assume input:
<student>Adam:101 Brad:110 Chad:111</student>
XSLT:
<?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="#all"
version="2.0">
<xsl:output indent="yes"/>
<xsl:template match="/">
<students>
<xsl:apply-templates/>
</students>
</xsl:template>
<xsl:template match="student">
<xsl:for-each select="tokenize(., ' ')">
<student variable="{substring-after(., ':')}">
<xsl:value-of select="substring-before(., ':')"/>
</student>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
OUTPUT:
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student variable="101">Adam</student>
<student variable="110">Brad</student>
<student variable="111">Chad</student>
</students>
Link: https://xsltfiddle.liberty-development.net/gVrvcxx

Merging and inheriting parameters

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.

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 2.0 collection and copy issue- not copying full file

using xslt 2.0.
Have a couple xmls
- i m trying to merge using xslt 2.0 and collection function
but it is copying only some of it and leaving the combined xml file not well formed.
Could this be a timeout issue, I did not think so since it takes only couple seconds to throw out error.
or a collection memory issue?
If i take just 1 file too, it is copying only most of the stuff but not all.
Any help is greatly appreciated.
Example:
<Response xmlns:a="http://example.com">
<FormInstance id="FORM1">
<element1>111-22-1122</element1>
</FormInstance>
<FormInstance id="FORM2">
<element1>111-22-1123</element1>
</FormInstance>
<FormInstance id="FORM3">
<element1>111-22-1124</element1>
</FormInstance>
</Response>
XSLT here:
<xsl:stylesheet version="2.0" 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" xmlns:a="http://example.com" xmlns:ext="http://exslt.org/common">
<xsl:output method="xml"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<Response>
<xsl:for-each select="collection('.?select=*.xml')">
<xsl:for-each select="/Response">
<xsl:copy-of select="node()"></xsl:copy-of>
</xsl:for-each>
</xsl:for-each>
</Response>
</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