How do I split and print values in xslt? - xslt-2.0

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

Related

XSLT 3.0 Convert String data type to Date type and apply translate to remove characters

Here is my source XML
<?xml version="1.0" encoding="UTF-8"?>
<Compensation>
<Salary>
<BasePay>$18600.1299</BasePay>
<PayDate>15-Mar-2022</PayDate>
<Bonus>$3500.99</Bonus>
<Gym>$670</Gym>
<Tax>$30,000.9912</Tax>
</Salary>
<Salary>
<BasePay>$28600.12</BasePay>
<PayDate>15-Mar-2022</PayDate>
<Bonus>$1500.99</Bonus>
<Gym/>
<Tax>$50,000</Tax>
</Salary>
</Compensation>
I am trying do following on my XML document
Format date to YYYY-MM-DD format. Currently date on my source XML is of string data type
Remove all currency and commas from whole document.
Here is my XSLT 3.0 solution which is working fine.
<?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"
xmlns:this="urn:this-stylesheet"
exclude-result-prefixes="xs this"
version="3.0">
<xsl:output method="xml" indent="yes"/>
<xsl:function name="this:fromatDate" as="xs:string">
<xsl:param name="dateString"/>
<xsl:variable name="month"
select="('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')"/>
<xsl:variable name="dd" select="substring($dateString,1,2)"/>
<xsl:variable name="mm"
select="format-number(index-of($month,substring($dateString,4,3)),'00')"/>
<xsl:variable name="yy" select="substring($dateString,8,4)"/>
<xsl:value-of select="format-date(xs:date( string-join(($yy, $mm, $dd), '-')),'[Y0001]-[M01]-[D01]')"/>
</xsl:function>
<xsl:mode on-no-match="shallow-copy"/>
<!-- Removes $ symbol and commas -->
<xsl:template match="text()">
<xsl:value-of select="translate(.,'$,', '')"/>
</xsl:template>
<!-- Convert string to date -->
<xsl:template match="PayDate">
<FormattedPayDate>
<xsl:value-of select="this:fromatDate(.)"/>
</FormattedPayDate>
</xsl:template>
</xsl:stylesheet>
I'm getting the expected result as below. However, I'd like to use anyone's help to know if there is any efficient way to write this code since I want to use XSLT 3.0.
I'm not sure if there is any functions in xpath 3.0 to handle string conversions to date and character removal.
<?xml version="1.0" encoding="UTF-8"?>
<Compensation>
<Salary>
<BasePay>18600.1299</BasePay>
<FormattedPayDate>2022-03-15</FormattedPayDate>
<Bonus>3500.99</Bonus>
<Gym>670</Gym>
<Tax>30000.9912</Tax>
</Salary>
<Salary>
<BasePay>28600.12</BasePay>
<FormattedPayDate>2022-03-15</FormattedPayDate>
<Bonus>1500.99</Bonus>
<Gym/>
<Tax>50000</Tax>
</Salary>
</Compensation>

Creating xsl:result-document with xpath 3.1 fn:transform using saxon 9.9 EE

I'd like to create an output document using the xpath 3.1 fn:transform. Following is A.xsl. It creates A.xml when run directly (from oxygen):
<?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"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output name="xml" method="xml" indent="true" />
<xsl:template name="xsl:initial-template">
<xsl:message select="'A'"/>
<xsl:result-document href="file:/C:/Work/test/A.xml" format="xml">
<resultDoc>
<text>The result of A.</text>
</resultDoc>
</xsl:result-document>
</xsl:template>
</xsl:stylesheet>
Result: A.xml is created with the desired output:
<?xml version="1.0" encoding="UTF-8"?>
<resultDoc>
<text>The result of A.</text>
</resultDoc>
Now, using the transform function to call A.xsl:
<?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="3.0">
<xsl:output name="xml" method="xml" encoding="UTF-8" indent="true" />
<!-- Global Constants -->
<xsl:variable name="xsl-file-base" select="'file:/C:/Work/test/'" as="xs:string"/>
<xsl:variable name="xsl-pipeline" select="'A.xsl'" as="xs:string"/>
<!-- Entry Point -->
<xsl:template name="xsl:initial-template">
<xsl:iterate select="$xsl-pipeline">
<xsl:variable name="file" select="$xsl-file-base || ." as="xs:string"/>
<xsl:result-document href="file:/C:/Work/test/A.xml" format="xml">
<xsl:sequence select="transform(map{'stylesheet-location' : $file})?output"/>
</xsl:result-document>
</xsl:iterate>
</xsl:template>
</xsl:stylesheet>
Result: A.xml is created but incomplete. Any help is appreciated.
<?xml version="1.0" encoding="UTF-8"?>
The result of the transform function is a map with an entry named output for the primary result document and further entries for secondary result documents. Your called stylesheet creates a secondary result with the URI file:/C:/Work/test/A.xml so
<xsl:sequence
select="transform(map{'stylesheet-location' : $file})('file:/C:/Work/test/A.xml')"/>
is more likely to produce an output.

How can we remove blank line without strip-space and indent XSLT2.0

I am trying remove blank lines without stripping spaces and indentation, because my requirement is indented XML. Can anyone help me, please?
Input XML
<?xml version="1.0" encoding="UTF-8"?>
<Export>
<bio id="b0001">
<p>Maciej</p>
</bio>
<funding-group>
<award-group>
<funding-source>
<named-content content-type="funder-name">Narodowe</named-content>
<named-content content-type="funderidentifier">501100004281</named-content>
</funding-source>
<award-id>/19/B/HS4/</award-id>
<award-id>3</award-id>
</award-group>
</funding-group>
</Export>
Expected Result
<?xml version="1.0" encoding="UTF-8"?>
<Export>
<bio id="b0001">
<p>Maciej</p>
</bio>
<funding-group>
<award-group>
<funding-source>
<named-content content-type="funder-name">Narodowe Centrum Nauki</named-content>
<named-content content-type="funderidentifier">10.13039/501100004281</named-content>
</funding-source>
<award-id>/19/B/HS4/</award-id>
<award-id>3</award-id>
</award-group>
</funding-group>
</Export>
XSLT I have tried this xsl but it's not worked properly if i have run this xsl then generate XML single line.
<?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:output method="xml" omit-xml-declaration="no"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//text()">
<xsl:value-of select="translate(.,'
', '')"/>
</xsl:template>
</xsl:stylesheet>

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

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>

Resources