XSLT: output " without it being parsed - parsing

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>

Related

Pass in reg ex as variable

I would like to pass in a regular express pattern through a variable i.e. as below:
<?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"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
exclude-result-prefixes="#all"
version="3.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="pattern" as="xs:string" select ="'[0-9]{4}'"/>
<xsl:variable name="caseSeries" >
<xsl:analyze-string select="'asdasd1980asdasd'" regex="$pattern">
<xsl:matching-substring>
<xsl:value-of select="."/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
<result>
<areaCode>
<xsl:value-of select="$caseSeries"/>
</areaCode>
</result>
</xsl:template>
</xsl:stylesheet>
but it's not matching anything, I tried escaping the curly bracket but it doesn't work. I'm planning on having an if statement to get the appropriate regex into a variable and apply that regex, so this is a POC in a way.
adding curly braces for the variable does the trick:
<xsl:analyze-string select="'asdasd1980asdasd'" regex="{$pattern}">
I updated the link to work

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

How to display latest date from N number of month in xslt?

I need to display latest date in N number of months using xslt.
My input:
2016/10/18
2016//10/15
2016/09/29
2016/09/15
and so on.
My output should be like below:
2016/10/18
2016/09/29
Can anyone help me on this?
Given a string of dates in that format you first need to tokenize to extract the date values, then you need to convert to the xs:date format, then you can group by the month and select the maximum value in each group. Using XSLT 3.0 that can be done as follows:
<?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:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="tokenize($input, '\s+')!xs:date(replace(., '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
In XSLT 2.0 you need to rewrite the date sequence construction a bit:
<?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="2.0">
<xsl:param name="input" as="xs:string">2016/10/18 2016/10/15 2016/09/29 2016/09/15</xsl:param>
<xsl:variable name="dates" as="xs:date*"
select="for $dateString in tokenize($input, '\s+') return xs:date(replace($dateString, '/', '-'))"/>
<xsl:variable name="max-dates" as="xs:date*">
<xsl:for-each-group select="$dates" group-by="month-from-date(.)">
<xsl:sort select="current-grouping-key()"/>
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:variable>
<xsl:template name="main" match="/">
<xsl:value-of select="$max-dates" separator="
"/>
</xsl:template>
</xsl:stylesheet>
I. Here is a short XSLT 2.0 solution:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select="current-group()[. eq max(current-group()/string())][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document (unordered and multi-year dates -- to make it more interesting):
<t>
<d>2016/10/15</d>
<d>2016/09/15</d>
<d>2016/10/18</d>
<d>2016/09/29</d>
<d>2017/09/17</d>
</t>
the wanted, correct result is produced:
<d>2016/10/18</d>
<d>2017/09/17</d>
II. If the date that has the same month's highest day is wanted -- regardless of the year, this transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:for-each-group select="d" group-by="substring(.,6,2)">
<xsl:sequence select=
"current-group()[substring(.,9,2) eq max(current-group()/substring(.,9,2))][1]"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (above), the correct result is produced:
<d>2016/10/18</d>
<d>2016/09/29</d>
III. If the dates are given together as a string:
Just use the tokenize() standard XPath 2.0 fy=unction.
For example, the equivalent of the first transformation above becomes:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDates"
select="'2016/10/15 2016/09/15 2016/10/18 2016/09/29 2017/09/17'"/>
<xsl:template match="/">
<xsl:for-each-group select="tokenize($vDates, '\s+')[.]" group-by="substring(.,6,2)">
<xsl:sequence select="max(current-group())"/>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>

parsing with XSLT

I am struggling a bit with a solution for the following problem and was hoping someone could point me in the right direction.
To illustrate the issue I will try and pair it down to its most simple form. I have the following data:
<?xml version="1.0" encoding="UTF-8"?>
<SampleData>
<Data>AA-BRAND1,BB-BRAND1,AA-BRAND2</Data>
</SampleData>
and need to produce the following output:
<?xml version="1.0" encoding="UTF-8"?>
<ListOfBrandSales>
<BrandSales>
<BrandChannel>AA</BrandChannel>
<ListOfBrand>
<Brand>BRAND1</Brand>
<Brand>BRAND2</Brand>
</ListOfBrand>
</BrandSales>
<BrandSales>
<BrandChannel>BB</BrandChannel>
<Brand>BRAND1</Brand>
</BrandSales>
</ListOfBrandSales>
I have been playing with the tokenize and distinct-values functions but am unable to get it. Seems like I need to nest these functions and not sure if it is possible. My apologies if the solution is obvious but I am a bit new to XSLT.
Thanks in advance.
How about:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="tokens" select="tokenize(/SampleData/Data, ',')" />
<xsl:variable name="channels">
<xsl:for-each select="$tokens">
<channel><xsl:value-of select="substring-before(., '-')"/></channel>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<ListOfBrandSales>
<xsl:for-each select="distinct-values($channels/channel)">
<BrandSales>
<BrandChannel><xsl:value-of select="."/></BrandChannel>
<ListOfBrand>
<xsl:for-each select="$tokens[starts-with(., current())]">
<Brand><xsl:value-of select="substring-after(., '-')"/></Brand>
</xsl:for-each>
</ListOfBrand>
</BrandSales>
</xsl:for-each>
</ListOfBrandSales>
</xsl:template>
</xsl:stylesheet>
Or, if you prefer:
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<ListOfBrandSales>
<xsl:for-each-group select="tokenize(SampleData/Data, ',')" group-by="substring-before(., '-')">
<BrandSales>
<BrandChannel><xsl:value-of select="current-grouping-key()"/></BrandChannel>
<ListOfBrand>
<xsl:for-each select="current-group()">
<Brand><xsl:value-of select="substring-after(., '-')"/></Brand>
</xsl:for-each>
</ListOfBrand>
</BrandSales>
</xsl:for-each-group>
</ListOfBrandSales>
</xsl:template>
</xsl:stylesheet>

XSLT conditionally write to two different files

I need to extract log meesages from an XML file and write them out to plain text files. The log messages come in two flavors, and I want to write them to separate files.
I have written a style sheet that does exactly what I need except that it sometimes creates empty files because the XML file may not contain messages of one type or another.
I am wondering, 1) if what I ma doing is the best method to do this, and 2) if there is a way to suppress empty files.
My sample may contain errors because it has been retyped. (the original is on a closed network)
Note: I am using XSLT 2.0 features.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text" encoding="iso-8859-1" />
<xsl:param name="break" select="string('
')" />
<xs:template match="/">
<xsl:result-document method="text" href="foo.txt">
<xsl:apply-templates select="Root/a/b/c[contains(., 'foo')]" />
</xsl:reult-document>
<xsl:result-document method="text" href="bar.txt">
<xsl:apply-templates select="Root/a/b/c[not(contains(., 'foo'))]" />
</xsl:reult-document>
</xsl:template>
<xsl:template match="*">
<xsl:value-of select=concat(normalize-space(.), $break)" />
</xsl:template>
</xsl:stylesheet>
You could use some XSLT 2.0 stylesheet like:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="break" select="string('
')" />
<xsl:template match="/">
<xsl:apply-templates select="Root/a/b/c"/>
</xsl:template>
<xsl:template match="/Root/a/b/c[contains(., 'foo')]">
<xsl:result-document method="text" href="foo.txt">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="/Root/a/b/c[not(contains(., 'foo'))]">
<xsl:result-document method="text" href="bar.txt">
<xsl:next-match/>
</xsl:result-document>
</xsl:template>
<xsl:template match="*">
<xsl:value-of select="concat(normalize-space(.), $break)" />
</xsl:template>
</xsl:stylesheet>
Note: Pattern matching and xsl:next-match.

Resources