Input file as follows:
<?xml version="1.0" encoding="UTF-8"?>
<!-- lower UPPER case -->
<document>
<rubbish> rubbish </rubbish>
<span class='lower'>
lower
<span class='upper'> upper </span>
case
</span>
</document>
Wanted output:
lower UPPER case
I know how to get the text included in the outer span with value-of, but this also
includes the string "upper" unchanged which is not what I want. I do not know how
to manipulate the text in the inner span and insert it in the middle of
the other text.
Failed attempt:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text" indent="no"/>
<xsl:template match="/">
<xsl:for-each select="//span[#class = 'lower']">
<xsl:if test="span/#class = 'upper'">
<xsl:text>do something</xsl:text> <!--TO DO -->
</xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You need to take a recursive approach here, for example:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:template match="text()[parent::span]">
<xsl:choose>
<xsl:when test="../#class='upper'">
<xsl:value-of select="translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
To understand how this works, read up on built-in template rules: http://www.w3.org/TR/xslt/#built-in-rule
The following approach does away with the <choose> and completely pushes the problem down to the match expression:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.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="text()"/>
<xsl:template match="text()[parent::span[#class = 'upper']]">
<xsl:value-of select="translate(., 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:template>
<xsl:template match="text()[parent::span[#class = 'lower']]">
<xsl:value-of select="translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')"/>
</xsl:template>
</xsl:stylesheet>
Related
I have a list of items in XML (mind the duplicates):
<root>
<a>hello</a>
<a>bye</a>
<a>5</a>
<a>hello</a>
<a>8</a>
</root>
I want to translate it to this:
<root>
<a>4</a>
<a>3</a>
<a>1</a>
<a>4</a>
<a>2</a>
</root>
Essentially, I'm replacing values with their positions in a sorted list of all values (comparing text to text). I'm trying to do this using <xsl:key>, but can't figure out how exactly.
Try perhaps:
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="/root">
<xsl:variable name="sorted">
<xsl:perform-sort select="a">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of(distinct-values($sorted/a), .)"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Or maybe a bit more elegantly:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<xsl:variable name="sorted" as="xs:string*">
<xsl:perform-sort select="distinct-values(a)">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of($sorted, .)"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Or even just simply:
<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="/root">
<xsl:variable name="sorted">
<xsl:perform-sort select="a">
<xsl:sort select="."/>
</xsl:perform-sort>
</xsl:variable>
<root>
<xsl:for-each select="a">
<a>
<xsl:value-of select="index-of($sorted/a, .)[1]"/>
</a>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
I'm using XSL to create a PDF document template, and don't want certain fields to display if the line value is zero.
I have tried
<xsl:if test="line_value != 0">
<xsl:with-param name="value" select="unit_quantity"/>
</xsl:if>
But this doesn't work. I think because line_value is of the format £0.00.
I'm trying to get it to do line_value NOT LIKE '£0.00', but I don't think that's the correct syntax for XSL.
I am assuming that below is your XML:
INPUT:
<?xml version="1.0" encoding="utf-8" ?>
<body>
<line_value>£0.00</line_value>
<line_value>£1.00</line_value>
<line_value>£0.00</line_value>
<line_value>£2.00</line_value>
<line_value>£0.00</line_value>
<line_value>£5.00</line_value>
<line_value>£0.00</line_value>
<line_value>£1.00</line_value>
</body>
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="xs" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="body/line_value">
<xsl:if test="number(translate(., '£', '')) != 0">
<num>
<xsl:value-of select="."/>
</num>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Below is what my XML looks like. I have also copied XSLT and output below. My problem is that, the output is always repeating the same first row. If I add more rows to input XML file then first row would repeat in output file for that many number of rows added. What could be the reason?
XML:
<Loans>
<Loan>
<loan_number>123</loan_number>
<loan_aqn_date>08-01-2016</loan_number>
</Loan>
<Loan>
<loan_number>456</loan_number>
<loan_aqn_date>10-01-2016</loan_number>
</Loan>
<Loan>
<loan_number>789</loan_number>
<loan_aqn_date>12-01-2016</loan_number>
</Loan>
</Loans>
Output:
loan_number|loan_aqn_date|
123|08-01-2016|
123|08-01-2016|
123|08-01-2016|
XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:text>loan_number|loan_aqn_date|</xsl:text>
<xsl:for-each select="/Loans/Loan">
<xsl:value-of select="concat(/Loans/Loan/loan_number,'|')" />
<xsl:value-of select="concat(/Loans/Loan/loan_aqn_date,'|')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
You are using an absolute path for "select" inside loop.
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:text>loan_number|loan_aqn_date|</xsl:text>
<xsl:for-each select="/Loans/Loan">
<xsl:value-of select="concat(loan_number,'|')" />
<xsl:value-of select="concat(loan_aqn_date,'|')" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Replacing the <for-each> with a template could make your approach more general.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:strip-space elements="Loans" /> <!-- Removes surrounding spaces -->
<xsl:template match="/Loans">
<xsl:text>loan_number|loan_aqn_date|
</xsl:text>
<xsl:apply-templates /> <!-- Processes 'Loan' nodes -->
</xsl:template>
<xsl:template match="Loan">
<xsl:value-of select="concat(loan_number, '|', loan_aqn_date,'|')" />
<xsl:text>
</xsl:text> <!-- Adds newlines -->
</xsl:template>
</xsl:stylesheet>
Output:
loan_number|loan_aqn_date|
123|08-01-2016|
456|10-01-2016|
789|12-01-2016|
I have the following XML:
<DEVICEMESSAGES>
<VERSION xml="1" checksum="" revision="0" envision="33050000" device="" />
<HEADER id1="0001" id2="0001" content="Nasher[<messageid>]: <!payload>" />
<MESSAGE level="7" parse="1" parsedefvalue="1" tableid="15" id1="24682" id2="24682" eventcategory="1003010000" content="Access to <webpage> was blocked due to its category (<info> by <hostname>)" />
</DEVICEMESSAGES>
I am using the following XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="DEVICEMESSAGES/HEADERS">
<xsl:value-of select="#id2"/>,<xsl:text/>
<xsl:value-of select="#content"/>,<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I get the following output:
0001 , Nasher[<messageid>]: <!payload>
whereas I need the column headings, too:
id2, content
0001 , Nasher[<messageid>]: <!payload>
If DEVICEMESSAGES is the document element and you have repeating MESSAGE elements, then this should work:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="DEVICEMESSAGES">
<xsl:text>id2,content,
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="DEVICEMESSAGES/HEADER">
<xsl:value-of select="#id2"/>,<xsl:text/>
<xsl:value-of select="#content"/>,<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
If you have a different document element, then adjust the template to match on that.
For instance, if the document element is doc and you had 1 to N number of DEVICEMESSAGES elements:
<doc>
<DEVICEMESSAGES>
<VERSION xml="1" checksum="" revision="0" envision="33050000" device="" />
<HEADER id1="0001" id2="0001" content="Nasher[<messageid>]: <!payload>" />
<MESSAGE level="7" parse="1" parsedefvalue="1" tableid="15" id1="24682" id2="24682" eventcategory="1003010000" content="Access to <webpage> was blocked due to its category (<info> by <hostname>)" />
</DEVICEMESSAGES>
<DEVICEMESSAGES>
<VERSION xml="1" checksum="" revision="0" envision="33050000" device="" />
<HEADER id1="0002" id2="0002" content="Nasher[<messageid>]: <!payload>" />
<MESSAGE level="7" parse="1" parsedefvalue="1" tableid="15" id1="24682" id2="24682" eventcategory="1003010000" content="Access to <webpage> was blocked due to its category (<info> by <hostname>)" />
</DEVICEMESSAGES>
</doc>
then you could use this:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="doc">
<xsl:text>id2,content,
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="DEVICEMESSAGES/HEADER">
<xsl:value-of select="#id2"/>,<xsl:text/>
<xsl:value-of select="#content"/>,<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
EDIT: Another alternative that uses the template match of the root node and does not need to know what the document element is:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/">
<xsl:text>id2,content,
</xsl:text>
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="DEVICEMESSAGES/HEADER">
<xsl:value-of select="#id2"/>,<xsl:text/>
<xsl:value-of select="#content"/>,<xsl:text/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
I have a list of items:
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
and I want as output
z
c
x
a
I have no order information in the file and I just want to reverse the lines. The last line in the source file should be first line in the output. How can I solve this problem with XSLT without sorting by the content of the items, which would give the wrong result?
I will present two XSLT solutions:
I. XSLT 1.0 with recursion Note that this solution works for any node-set, not only in the case when the nodes are siblings:
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<xsl:call-template name="reverse">
<xsl:with-param name="pList" select="*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pList"/>
<xsl:if test="$pList">
<xsl:value-of
select="concat($pList[last()], '
')"/>
<xsl:call-template name="reverse">
<xsl:with-param name="pList"
select="$pList[not(position() = last())]"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</t>
produces the wanted result:
z
c
x
a
II. XSLT 2.0 solution :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output method="text"/>
<xsl:template match="/*">
<xsl:value-of select="reverse(*)/string(.)"
separator="
"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the same XML document, the same correct result is produced.
XML CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<device>
<element>a</element>
<element>x</element>
<element>c</element>
<element>z</element>
</device>
XSLT CODE:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- Edited by XMLSpy® -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//device">
<xsl:for-each select="element">
<xsl:sort select="position()" data-type="number" order="descending"/>
<xsl:text> </xsl:text>
<xsl:value-of select="."/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
note: if you're using data-type="number", and any of the values aren't numbers, those non-numeric values will sort before the numeric values. That means if you're using order="ascending", the non-numeric values appear first; if you use order="descending", the non-numeric values appear last.
Notice that the non-numeric values were not sorted; they simply appear in the output document in the order in which they were encountered.
also, you may find usefull to read this:
http://docstore.mik.ua/orelly/xml/xslt/ch06_01.htm
Not sure what the full XML looks like, so I wrapped in a <doc> element to make it well formed:
<doc>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</doc>
Running that example XML against this stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="doc/item[position()=last()]" />
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="item" />
<xsl:value-of select="$item" />
<!--Adds a line feed-->
<xsl:text>
</xsl:text>
<!--Move on to the next item, if we aren't at the first-->
<xsl:if test="$item/preceding-sibling::item">
<xsl:call-template name="reverse">
<xsl:with-param name="item" select="$item/preceding-sibling::item[1]" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Produces the requested output:
z
c
x
a
You may need to adjust the xpath to match your actual XML.
Consider this XML input:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item>a</item>
<item>x</item>
<item>c</item>
<item>z</item>
</items>
The XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="/items[1]">
<xsl:variable name="items-list" select="." />
<xsl:variable name="items-count" select="count($items-list/*)" />
<xsl:for-each select="item">
<xsl:variable name="index" select="$items-count+1 - position()"/>
<xsl:value-of select="$items-list/item[$index]"/>
<xsl:value-of select="'
'"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
And the result:
z
c
x
a