I want to ask how to get total amount if we use substring function,
for example I have this xml,
<document>
<line id="0">
<field id="0"><![CDATA[AAAddd17aaass]]></field>
</line>
<line id="1">
<field id="0"><![CDATA[DDDaaa33sssaa]]></field>
</line>
</document>
I should sum(substring(field[#id='0'], 7,2)). Then I try to do that, I get this error message: Current Item is 'NaN of type xs:string. (I try to use number function but it doesn't help)
Please advice me how to solve it.
Easy in XSLT 2.0:
sum(field[#id='0']/number(substring(., 7,2)))
Rather more difficult in XSLT 1.0: see for example http://www.velocityreviews.com/forums/t170401-sum-over-computed-value.html
In XSLT 1.0 if you don't want to write recursive templates yourself, you can use the transform-and-sum template from FXSL.
See how to use it here.
Here is the full transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:func-transform="f:func-transform"
exclude-result-prefixes="xsl f func-transform"
>
<xsl:import href="transform-and-sum.xsl"/>
<!-- to be applied on testTransform-and-sum.xml -->
<xsl:output method="text"/>
<func-transform:func-transform/>
<xsl:template match="/">
<xsl:call-template name="transform-and-sum">
<xsl:with-param name="pFuncTransform"
select="document('')/*/func-transform:*[1]"/>
<xsl:with-param name="pList" select="/*/*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="func-transform:*" mode="f:FXSL">
<xsl:param name="arg1" select="0"/>
<xsl:value-of select="number(substring($arg1, 7,2))"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<line id="0">
<field id="0"><![CDATA[AAAddd17aaass]]></field>
</line>
<line id="1">
<field id="0"><![CDATA[DDDaaa33sssaa]]></field>
</line>
</document>
the wanted, correct result is produced:
50
Related
After spending a couple of days on trying to find a solution using muenchian grouping I desperately need some help in figuring out the proper xslt code for transforming my XML so that it will produce a unique list of years for all shows (to be imported into Solr eventually). Basically I need the unique grandchildren of a node to be grouped, but somehow all existing solutions on SO did not work for me.
Here's my source XML:
<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="http://localhost/default.xsl" ?>
<DIRECTORY>
<SHOWS>
<SHOW>
<TITLE>Child's play</TITLE>
<EVENTS>
<EVENT>
<YEAR>2014</YEAR>
<TITLE>Gala day</TITLE>
</EVENT>
<EVENT>
<YEAR>2014</YEAR>
<TITLE>Gala night</TITLE>
</EVENT>
<EVENT>
<YEAR>2015</YEAR>
<TITLE>Gala night</TITLE>
</EVENT>
</EVENTS>
</SHOW>
</SHOWS>
</DIRECTORY>
Here's the desired output:
<?xml version="1.0"?>
<add>
<doc>
<field name="show_title">Child's play</field>
<field name="show_year">2014</field>
<field name="show_year">2015</field>
</doc>
</add>
Here's my XSLT that does not work and is based on various answers around Stack Overflow
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
<xsl:key name="show_key"
match="TITLE"
use="TITLE"/>
<xsl:key name="show_years_key"
match="TITLE"
use="concat(TITLE, ' ',
EVENTS/EVENT/YEAR)"/>
<xsl:template match="/">
<add>
<xsl:for-each select="/DIRECTORY/SHOWS/SHOW">
<doc>
<xsl:call-template name="show_info"/>
</doc>
</xsl:for-each>
</add>
</xsl:template>
<xsl:template name="show_info">
<field name="show_title">
<xsl:value-of select="TITLE"/>
</field>
<xsl:variable
name="show_events"
select="key('show_key', TITLE)"/>
<xsl:for-each
select="$show_events[generate-id() =
generate-id(
key('show_years_key',
concat(TITLE,
' ',
EVENTS/EVENT/YEAR))[1])]">
<field name="show_years">
<xsl:value-of select="EVENTS/EVENT/YEAR"/>
</field>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
While I really appreciate any help to get to a working solution I could also use some pointers as to why my solution does not work when it should. I left the minimal example a bit more complex on purpose as I suspect my xslt organization might make things more complicated, but the overall solution works quite well, even with multiple different templates.
The grouping key needs to match the elements you're trying to group (i.e. the YEAR elements). I'm having a bit of trouble following your call-template logic, I'd approach it more like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no"/>
<xsl:key name="show_years_key"
match="YEAR"
use="concat(ancestor::SHOW[1]/TITLE, ' ', .)"/>
<xsl:template match="/">
<add>
<xsl:apply-templates select="/DIRECTORY/SHOWS/SHOW" />
</add>
</xsl:template>
<xsl:template match="SHOW">
<doc>
<field name="show_title">
<xsl:value-of select="TITLE"/>
</field>
<!-- current() here is the SHOW element this template applies to -->
<xsl:for-each select="EVENTS/EVENT/YEAR[generate-id() = generate-id(
key('show_years_key', concat(current()/TITLE, ' ', .))[1])]">
<field name="show_years">
<xsl:value-of select="." />
</field>
</xsl:for-each>
</doc>
</xsl:template>
</xsl:stylesheet>
The important point is that you're grouping the YEAR elements by the TITLE of their containing SHOW, not the SHOW elements by the YEAR they contain.
I want to ask. there is posible to add condition which will be checking xml data with lookup table, and if we didnt't have value in lookup table add const 8 to output?
xslt Code:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="Department" match="Department" use="../Collection"/>
<xsl:template match="/">
<document>
<xsl:apply-templates/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="inputDep" select="field[#id='3']"/>
<Department>
<xsl:for-each select="document('lookup.xml')">
<xsl:for-each select="key('Deparment',$inputDep)">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:for-each>
</Department>
</xsl:template>
</xsl:stylesheet>
lookup table:
<document>
<line-item>
<Collection>1</Collection>
<Department>3</Department>
</line-item>
<line-item>
<Collection>2</Collection>
<Department>1</Department>
</line-item>
<line-item>
<Collection>3</Collection>
<Department>2</Department>
</line-item>
</document>
xml file:
<document>
<line id="0">
<field id="3"><![CDATA[1]]></field>
</line>
<line id="1">
<field id="3"/>
</line>
<line id="2">
<field id="3"/><![CDATA[4]]></field>
</line>
</document>
result:
<Department>3<Department>
<Department>8<Department>
<Department>8<Department>
You could assign the looked-up value to a variable and choose what to output based on whether anything was found.
Edit 2: A full demonstration stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="Department" match="Department" use="../Collection"/>
<xsl:template match="/">
<document>
<xsl:apply-templates/>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:variable name="inputDep" select="field[#id='3']"/>
<Department>
<xsl:for-each select="document('lookup.xml')">
<xsl:variable name="value" select="key('Department',$inputDep)"/>
<xsl:choose>
<xsl:when test="$value">
<xsl:value-of select="$value"/> <!-- see note -->
</xsl:when>
<xsl:otherwise>8</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</Department>
</xsl:template>
</xsl:stylesheet>
Note: Replaced the xsl:for-each loop in the original stylesheet with a simple xsl:value-of, assuming that the looping the values was not intentional. If it actually was, you can replace this with a for-each loop.
I have an XML file. I should find for same field2 start(min) date and End(max) date from field3. Maybe xsl have some function to find it.Because I try do it trying to find min month and min day and max month, max day.
XML:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">2011-04-04</field>
</line>
<line id="2">
<field id="2">X111</field>
<field id="3">2011-04-02</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
<line id="4">
<field id="2">X222</field>
<field id="3">2011-04-01</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">2011-04-01</field>
</line>
</document>
Output:
<document>
<Message>
<ID>X111</ID>
<dateStart>2011-03-31</dateStart>
<dateEnd>2011-04-04</dateEnd>
</Message>
<Message>
<ID>X222</ID>
<dateStart>2011-04-01</dateStart>
<dateEnd>2011-04-04</dateEnd>
</Message>
<Message>
<ID>X333</ID>
<dateStart>2011-04-01</dateStart>
<dateEnd>2011-04-01</dateEnd>
</Message>
</document>
Please help to solve it. I'm working with stylesheet version="1.0".
This can probably be optimized but it returns the requested results:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="test" match="line" use="field[#id=2]"/>
<xsl:template match="/">
<document>
<xsl:for-each select="//line[generate-id()=generate-id(key('test',field[#id=2]))]">
<xsl:sort select="field[#id=2]"/>
<Message>
<ID>
<xsl:value-of select="field[#id=2]"/>
</ID>
<xsl:apply-templates select="key('test',field[#id=2])">
<xsl:sort select="field[#id=3]"/>
</xsl:apply-templates>
</Message>
</xsl:for-each>
</document>
</xsl:template>
<xsl:template match="line">
<xsl:if test="position()=1">
<dateStart>
<xsl:value-of select="field[#id=3]"/>
</dateStart>
</xsl:if>
<xsl:if test="position()=last()">
<dateEnd>
<xsl:value-of select="field[#id=3]"/>
</dateEnd>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
I upvoted #mousio's answer, but I'd prefer to see the first of each line type handled in its own template. So, in the spirit of TMTOWTDI, here's how I would have done it:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes" />
<xsl:key name="byField2" match="line" use="field[#id=2]" />
<xsl:template match="/">
<document>
<xsl:apply-templates select="document/line" />
</document>
</xsl:template>
<xsl:template match="line[count(.|key('byField2', field[#id=2])[1])=1]">
<Message>
<ID><xsl:value-of select="field[#id=2]" /></ID>
<xsl:apply-templates select="key('byField2', field[#id=2])" mode="m">
<xsl:sort select="field[#id=3]" />
</xsl:apply-templates>
</Message>
</xsl:template>
<xsl:template match="line" mode="m">
<xsl:if test="position()=1">
<dateStart><xsl:value-of select="field[#id=3]" /></dateStart>
</xsl:if>
<xsl:if test="position()=last()">
<dateEnd><xsl:value-of select="field[#id=3]" /></dateEnd>
</xsl:if>
</xsl:template>
<xsl:template match="line" />
</xsl:stylesheet>
I think this is easier to read (and probably more efficient on large documents, since it doesn't abuse //).
This transformation shows how to find the wanted minimum and maximum:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:apply-templates select="line">
<xsl:sort select="field[#id=3]"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="line">
<xsl:if test="position()=1">
Earliest:
<xsl:copy-of select="."/>
</xsl:if>
<xsl:if test="position()=last()">
Latest:
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
<line id="1">
<field id="2">X111</field>
<field id="3">2011-04-04</field>
</line>
<line id="2">
<field id="2">X111</field>
<field id="3">2011-04-02</field>
</line>
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
<line id="4">
<field id="2">X222</field>
<field id="3">2011-04-01</field>
</line>
<line id="4">
<field id="2">X333</field>
<field id="3">2011-04-01</field>
</line>
</document>
produces:
Earliest:
<line id="0">
<field id="2">X111</field>
<field id="3">2011-03-31</field>
</line>
Latest:
<line id="3">
<field id="2">X222</field>
<field id="3">2011-04-04</field>
</line>
Explanation:
Choosing the first and last element from the sorted node-list. The dates are in a "good" format, so they are sorted just as strings.
I have to transform xml file where i should check field id '0', field id '1' and sum field id '2'. For example I have:
<document>
<line id="0">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">2</field>
</line>
<line id="1">
<field id="0">MAR</field>
<field id="1">doc2</field>
<field id="2">3</field>
</line>
<line id="2">
<field id="0">AAA></field>
<field id="1">doc4</field>
</line>
<line id="3">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">4</field>
</line>
</document>
result should be:
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
there I should take all MAR lines, and show some results which are depends of field id '1'.
My idea was, first off all do cycle(for each) and use condition(when). Maybe somebody offer more omptimal decision.
I add new note, how to check if data comes like that:
<field id="0">MAR999</field>
<field id="1">doc1-1231</field>
First field i try to use function contains 'MAR', others substring-before '-'. but I stuck when I try it use on Yours program. maybe you can take some advice for it?
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:strip-space elements="*"/>
<xsl:key name="kLineById0Id1" match="line[field[#id=2]]"
use="concat(field[#id=0],'+',field[#id=1])"/>
<xsl:template match=
"line[field[#id=2]
and
generate-id()
=
generate-id(key('kLineById0Id1',
concat(field[#id=0],
'+',field[#id=1])
)[1])
]
">
<xsl:element name="type-{field[#id=0]}">
<document>
<xsl:value-of select="field[#id=1]"/>
</document>
<sum>
<xsl:value-of select=
"sum(key('kLineById0Id1',
concat(field[#id=0],
'+',field[#id=1])
)
/field[#id=2]
)
"/>
</sum>
</xsl:element>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
when applied on the provided XML document:
<document>
<line id="0">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">2</field>
</line>
<line id="1">
<field id="0">MAR</field>
<field id="1">doc2</field>
<field id="2">3</field>
</line>
<line id="2">
<field id="0">AAA></field>
<field id="1">doc4</field>
</line>
<line id="3">
<field id="0">MAR</field>
<field id="1">doc1</field>
<field id="2">4</field>
</line>
</document>
produces the wanted, correct result:
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
Explanation: The Muenchian method for grouping is used with the key defined as the concatenation of two elements.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kLine" match="line" use="field[#id='1']"/>
<xsl:template match="/*">
<r>
<xsl:apply-templates select="line
[field[#id='0'] = 'MAR']
[count(
. | key('kLine', field[#id='1'])[1]
) = 1]
"/>
</r>
</xsl:template>
<xsl:template match="line">
<type-MAR>
<document>
<xsl:value-of select="field[#id='1']"/>
</document>
<sum>
<xsl:value-of select="
sum(
key('kLine', field[#id='1'])/
field[#id='2']
)"/>
</sum>
</type-MAR>
</xsl:template>
</xsl:stylesheet>
Correct against your sample will be:
<r>
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
</r>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kLineById0-Id1" match="line"
use="concat(field[#id='0'],'+',field[#id='1'])"/>
<xsl:param name="pId0" select="'MAR'"/>
<xsl:template match="document">
<result>
<xsl:apply-templates select="line[generate-id()=
generate-id(
key('kLineById0-Id1',
concat($pId0,
'+',
field[#id='1']
)
)[1]
)]"/>
</result>
</xsl:template>
<xsl:template match="line">
<xsl:element name="type-{$pId0}">
<document>
<xsl:value-of select="field[#id='1']"/>
</document>
<sum>
<xsl:value-of select="sum(key('kLineById0-Id1',
concat(field[#id='0'],
'+',
field[#id='1']
)
)/field[#id='2']
)"/>
</sum>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Output:
<result>
<type-MAR>
<document>doc1</document>
<sum>6</sum>
</type-MAR>
<type-MAR>
<document>doc2</document>
<sum>3</sum>
</type-MAR>
</result>
Note: Grouping by both #id attributes, sum group, dynamic element name, parameterized first #id.
Thanks for answers, I use Flack decision and make some correction:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="kLine" match="line" use="substring(field[#id='1'],1,4)"/>
<xsl:template match="/*">
<document>
<xsl:apply-templates select="line[contains(field[#id='0'], 'MAR')][count(. | key('kLine', substring(field[#id='1'],1,4))[1]) = 1]"/>
</document>
</xsl:template>
<xsl:template match="line">
<type-MAR>
<document>
<xsl:value-of select="substring(field[#id='1'],1,4)"/>
</document>
<sum>
<xsl:value-of select="sum(key('kLine', substring(field[#id='1'],1,4))/field[#id='2'])"/>
</sum>
</type-MAR>
</xsl:template>
</xsl:stylesheet>
Dimitre and Alejandro decisions are also good and useful(maybe more professional). But Dimitre more concentrate on my task which I wrote, for example he use condition to check if we have the second field(I didn't wrote that not only MAR could have field2). Alejandro to check it use parameter, so for me it was a good lesson to search more information how to use it, because with xsl language I have less than one month experience. So for me was difficult to prepair Yours programs for my work. Flack text was more understandable for me as a beginner.
I have one set of documents that implicitly define the allowed fields for a second set of objects that have to be transformed into a third set of documents (
which "rules" document to use depends upon the content of the file being transformed) e.g.
<!-- One example rules document -->
<document object="obj1_rules">
<field name="A"/>
<field name="B"/>
<field name="C"/>
</document>
<!-- Document to be tranformed based upon obj1_rules-->
<document object="obj1">
<field name="A"/>
<field name="B"/>
<field name="C"/>
<field name="D"/>
<field name="E"/>
</document>
<!-- Desired result-->
<document object="obj1">
<newfield name="A"/>
<newfield name="B"/>
<newfield name="C"/>
</document>
Is it possible to do this transformation using xslt?
I see that "There is no way in XSLT of constructing XPath expressions (e.g. variable references) at run-time." So I am out of luck, or I am just looking at this problem incorrectly? Thanks!
Here is a simple solution:
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:strip-space elements="*"/>
<!-- -->
<xsl:variable name="vrtfRules">
<document object="obj1_rules">
<field name="A"/>
<field name="B"/>
<field name="C"/>
</document>
</xsl:variable>
<!-- -->
<xsl:variable name="vRules" select=
"document('')/*/xsl:variable
[#name = 'vrtfRules']
/*
"/>
<!-- -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<!-- -->
<xsl:template match="field">
<xsl:if test="#name = $vRules/*/#name">
<newfield>
<xsl:apply-templates select="node()|#*"/>
</newfield>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the originaly-provided source XML document:
<document object="obj1">
<field name="A"/>
<field name="B"/>
<field name="C"/>
<field name="D"/>
<field name="E"/>
</document>
produces the desired result:
<document object="obj1">
<newfield name="A"/>
<newfield name="B"/>
<newfield name="C"/>
</document>
Note that the "rules document" is within the stylesheet just for compactness. When it is a separate document, just the document() function used will need to be adjusted with the actual href.
Maybe I'm oversimplifying, but is there a reason why your "rules document" cannot simply be an XSLT?
Well, I can see why you'd like to have rules in a simple xml file rather than in a full-fledged xsl stylesheet but you're simply skipping a step.
You need to make a xsl stylesheet that will transform your xml rule document into a xsl stylesheet that you will then apply onto your source xml.
The trick is with namespaces and not getting confused by the mix of xsl rules applied and xsl rules generated.
<?xml version="1.0" ?>
<xsl:stylesheet
xmlns="YOUR_NAMESPACE_HERE"
xmlns:output="http://www.w3.org/1999/XSL/Transform"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output
method="xml"
indent="yes"
media-type="text/xsl" />
<xsl:template match="/">
<output:stylesheet version="2.0">
<xsl:apply-templates />
</output:stylesheet>
</xsl:template>
<xsl:template match="document[#object]">
<output:template match="document[#object='{#object}']">
<output:copy>
<xsl:apply-templates />
</output:copy>
</output:template>
</xsl:template>
<xsl:template match="field[#name]">
<output:if test="field[#name='{#name}']">
<output:copy-of select="field[#name='{#name}']" />
</output:if>
</xsl:template>
</xsl:stylesheet>
I presumed you'd use the same document object attribute in the rules and in the documents themselves (this is much simpler imo).
So, you run your rules document through the stylesheet above. The result is a new xsl stylesheet that does exactly what you describe in your xml rule document. You then apply this resulting stylesheet onto your source document and you should get the result you expect.