Take a look at the following code:
<xsl:template match="tocline[#toclevel='2']">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:for-each select="descendant::toctitle">
<xsl:if test="position() = last()">
<xsl:attribute name="last">
<xsl:value-of select="'true'"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
This template applies the attribute to the tocline element. I want it to apply the attribute to the last toctitle in the nodeset, which can be located at different levels.
With this sample:
<tocline id="d1e11" toclevel="1">
<toctitle>Section 1. Legislative Powers</toctitle>
<tocline id="d1e40" toclevel="2">
<toctitle>Separation of Powers and Checks and Balances</toctitle>
<tocline id="d1e51" toclevel="3">
<toctitle>The Theory Elaborated and Implemented</toctitle>
</tocline>
<tocline id="d1e189" toclevel="3">
<toctitle>Judicial Enforcement</toctitle>
</tocline>
</tocline>
</tocline>
I want this:
<tocline id="d1e11" toclevel="1">
<toctitle>Section 1. Legislative Powers</toctitle>
<tocline id="d1e40" toclevel="2">
<toctitle>Separation of Powers and Checks and Balances</toctitle>
<tocline id="d1e51" toclevel="3">
<toctitle>The Theory Elaborated and Implemented</toctitle>
</tocline>
<tocline id="d1e189" toclevel="3">
<toctitle last="true">Judicial Enforcement</toctitle>
</tocline>
</tocline>
</tocline>
But I get this:
<tocline id="d1e11" toclevel="1">
<toctitle>Section 1. Legislative Powers</toctitle>
<tocline id="d1e40" toclevel="2" last="true">
<toctitle>Separation of Powers and Checks and Balances</toctitle>
<tocline id="d1e51" toclevel="3">
<toctitle>The Theory Elaborated and Implemented</toctitle>
</tocline>
<tocline id="d1e189" toclevel="3">
<toctitle>Judicial Enforcement</toctitle>
</tocline>
</tocline>
</tocline>
I added a key, and here's what I came up with:
<xsl:key name="l2id" match="tocline[#toclevel eq '2']" use="#id"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="toctitle[. is (ancestor-or-self::tocline/key('l2id',#id)/descendant-or-self::toctitle[last()])]">
<xsl:copy>
<xsl:attribute name="last">
<xsl:value-of select="'true'"/>
</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
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:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"toctitle
[. is (ancestor::tocline[#toclevel eq '2'][1]//toctitle)[last()]]">
<toctitle last="true">
<xsl:apply-templates select="#*|node()"/>
</toctitle>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<tocline id="d1e11" toclevel="1">
<toctitle>Section 1. Legislative Powers</toctitle>
<tocline id="d1e40" toclevel="2">
<toctitle>Separation of Powers and Checks and Balances</toctitle>
<tocline id="d1e51" toclevel="3">
<toctitle>The Theory Elaborated and Implemented</toctitle>
</tocline>
<tocline id="d1e189" toclevel="3">
<toctitle>Judicial Enforcement</toctitle>
</tocline>
</tocline>
</tocline>
produces the wanted, correct result:
<tocline id="d1e11" toclevel="1">
<toctitle>Section 1. Legislative Powers</toctitle>
<tocline id="d1e40" toclevel="2">
<toctitle>Separation of Powers and Checks and Balances</toctitle>
<tocline id="d1e51" toclevel="3">
<toctitle>The Theory Elaborated and Implemented</toctitle>
</tocline>
<tocline id="d1e189" toclevel="3">
<toctitle last="true">Judicial Enforcement</toctitle>
</tocline>
</tocline>
</tocline>
Related
trying to split the xml file having multiple elements into separate xml having different elements.
Input File:
<Person>
<firstname>ABC</firstname>
<lastname>ABC</lastname>
<address>address1</address>
<address>address2</address>
<city>city</city>
<state>state</state>
<currency>currency1</currency>
<currency>currency2</currency>
</Person>
Need to split above file into two files as
Output file-1
<Person>
<firstname>ABC</firstname>
<lastname>ABC</lastname>
<address>address1</address>
<city>city</city>
<state>state</state>
<currency>currency1</currency>
</Person>
Output file -2
<Person>
<firstname>ABC</firstname>
<lastname>ABC</lastname>
<address>address2</address>
<city>city</city>
<state>state</state>
<currency>currency2</currency>
</Person>
Here's a generic solution that outputs N files where N is the maximum number of same-name children elements, where file N contains the Nth instance of each element name if there are at least N, or the first one otherwise:
<xsl:template match="/*">
<xsl:variable name="this" select="."/>
<xsl:variable name="names" select="distinct-values(*/name())"/>
<xsl:for-each select="1 to max(
for $name in $names return count(*[name()=$name]))"/>
<xsl:variable name="n" select="."/>
<xsl:result-document href="file{.}">
<xsl:element name="{name($this)}">
<xsl:for-each-group select="$this/*" group-by="name()">
<xsl:copy-of select="(current-group()[$n], .)[1]"/>
</xsl:for-each-group>
</xsl:element>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
This should do what you want with the example input you have shown, but whether it does the right thing with any other input is anyone's guess, because you have under-specified the requirements.
I have an xml like this (see below).
Using xslt2.0, I need to find whether any one contain a mix of positive and negative numbers ?
Weights: concantenated string of positive/negative numbers.. (separator = ;).
<PriceInfo>
<price>
<date>20160124</date>
<weights>1;2;5;4;</weights>
</price>
<price>
<date>20160125</date>
<weights>1;2;3;4;</weights>
</price>
<price>
<date>20160126</date>
<weights>1;-2;3;4;</weights>
</price>
</PriceInfo>
Thanks
Well, using tokenize you can extract the tokens between ;, you can then check whether they are integers, if so, convert them, and then you can check whether there are any greater than and any smaller than zero:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="weights">
<xsl:copy>
<xsl:variable name="integers" select="for $token in tokenize(., ';')[. castable as xs:integer] return xs:integer($token)"/>
<xsl:sequence select="(some $i in $integers satisfies $i gt 0) and (some $j in $integers satisfies $j lt 0)"/>
</xsl:copy>
</xsl:template>
</xsl:transform>
I modified the answer from Martin to suit my needs.
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:template match="/">
<xsl:variable name="allWeights" select="string-join(//weights/text(), '')" />
<xsl:variable name="weightTokens" select="for $token in tokenize($allWeights, ';')[. castable as xs:string] return xs:string($token)"/>
<xsl:variable name="isMixOfPositiveNegativeWeights" select="(some $posToken in $weightTokens satisfies matches($posToken, '[0-9].*')) and (some $negToken in $weightTokens satisfies matches($negToken, '-.*'))"/>
<xsl:value-of select="$isMixOfPositiveNegativeWeights" />
</xsl:template>
</xsl:transform>
The following xslt code produces the output incorrectly. Actually, it should increment the values by 1, But, It produces increment by 2. I need to get this know why. Could anyone let me know why this?
the xml input is
<AAA>
<BBB>cc </BBB>
<BBB>ff </BBB>
<BBB>aa </BBB>
<BBB>fff </BBB>
<BBB>FFF </BBB>
<BBB>Aa </BBB>
<BBB>ccCCC </BBB>
</AAA>
and the xslt input code is
<xsl:template match="/">
<xsl:text>
BBB[</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>]: </xsl:text>
<xsl:value-of select="."/>
</xsl:template>
It produces the output as follows [wrongly], but it should provide such as [1], [2], [3] etc.
BBB[2]: cc
BBB[4]: ff
BBB[8]: aa
BBB[10]: fff
BBB[12]: FFF
BBB[14]: Aa
BBB[16]: ccCCC
Any idea?
I am pretty sure if you only have <xsl:template match="/"> that then you won't even get the output you say you get.
Assuming you have
<xsl:template match="BBB">
<xsl:text>
BBB[</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>]: </xsl:text>
<xsl:value-of select="."/>
</xsl:template>
then the result depends on other factors like whether you have <xsl:strip-space elements="*"/> or whether you use
<xsl:template match="AAA">
<xsl:apply-templates select="*"/>
</xsl:template>
Your current result you have suggests you are not stripping white space text nodes and you either rely on built-in templates or you have <xsl:apply-templates/> or <xsl:apply-templates select="node()"/> in the template matching AAA. That way the current node list contains both element node as well as text nodes (between element nodes) resulting in your position results 2, 4, 6, ...
I would fix the code with
<xsl:template match="BBB">
<xsl:text>
BBB[</xsl:text>
<xsl:number/>
<xsl:text>]: </xsl:text>
<xsl:value-of select="."/>
</xsl:template>
I have a XML like this:
<?xml version="1.0" encoding="UTF-8"?>
<nodes>
<n c="value2"/>
<n>Has a relation to node with value2</n>
<n>Has a relation to node with value2</n>
<n c="value"/>
<n>Has a relation to node with value</n>
<n c="value1"/>
<n>Has a relation to node with value1</n>
</nodes>
I sort all elements which have attributes in variable, then I iterate over this variable in for-each loop. But at the end of each loop, I need to print value of those elements which are below the currently processed element(in original XML) and have no atrribute.
That means: call apply-templates on <n> without attribute, but the "select" attr. in apply-templates does not work, probably because I´m now in variable loop.
Is there a solution for that?
Thanks
Here is the XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="nodes">
<xsl:variable name="sorted">
<xsl:for-each select="n[#c]">
<xsl:sort select="#c"></xsl:sort>
<xsl:copy-of select="."></xsl:copy-of>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$sorted/n">
<xsl:value-of select="#c"></xsl:value-of>
<xsl:apply-templates select="/nodes/n[2]"></xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="n[not(#c)]">
<xsl:value-of select="."></xsl:value-of>
</xsl:template>
</xsl:stylesheet>
This is just example,all this is a part of bigger project:)
Desired output with a more complicated XPAth(now even the simple one does not work) is:
Value
Has a relation to node with value
Value1
Has a relation to node with value1
Value2
Has a relation to node with value2
Has a relation to node with value2
Is it a bit clearer now?
Some thoughts: apply-templates without a select processes the child node of the current context node; in your input sample the n elements do not have any children at all. Furthermore in your variable you do a copy-of meaning you create new nodes that have no relation to the nodes in the input sample. So while I am not sure what you want to achieve your construction with apply-templates inside the for-each does not make sense, given the input sample you have posted and the variable you use.
I suspect you could use the XSLT 2.0 for-each-group group-starting-with as in
<xsl:template match="nodes">
<xsl:for-each-group select="n" group-starting-with="n[#c]">
<xsl:sort select="#c"/>
<xsl:value-of select="#c"/>
<xsl:apply-templates select="current-group() except ."/>
</xsl:for-each-group>
</xsl:template>
If that does not help then consider to post a small input sample with sample data and the corresponding output sample you want to create with XSLT 2.0, then we can make suggestions on how to achieve that.
[edit] Now that you have posted an output sample I post an enhanced version of my previous suggestion:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="text"/>
<xsl:template match="nodes">
<xsl:for-each-group select="n" group-starting-with="n[#c]">
<xsl:sort select="#c"/>
<xsl:value-of select="#c"/>
<xsl:text>
</xsl:text>
<xsl:apply-templates select="current-group() except ."/>
</xsl:for-each-group>
</xsl:template>
<xsl:template match="n[not(#c)]">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
When I use Saxon 9.3 and run the stylesheet against your latest input sample the result is as follows:
value
Has a relation to node with value
value1
Has a relation to node with value1
value2
Has a relation to node with value2
Has a relation to node with value2
That is what you asked for I think so try that approach with your more complex real input.
I am using the below XSL 2.0 code to find the ids of the text nodes that contains the list of indices that i give as input. the code works perfectly but in terms for performance it is taking a long time for huge files. Even for huge files if the index values are small then the result is quick in few ms. I am using saxon9he Java processor to execute the XSL.
<xsl:variable name="insert-data" as="element(data)*">
<xsl:for-each-group
select="doc($insert-file)/insert-data/data"
group-by="xsd:integer(#index)">
<xsl:sort select="current-grouping-key()"/>
<data
index="{current-grouping-key()}"
text-id="{generate-id(
$main-root/descendant::text()[
sum((preceding::text(), .)/string-length(.)) ge current-grouping-key()
][1]
)}">
<xsl:copy-of select="current-group()/node()"/>
</data>
</xsl:for-each-group>
</xsl:variable>
In the above solution if the index value is too huge say 270962 then the time taken for the XSL to execute is 83427ms. In huge files if the index value is huge say 4605415, 4605431 it takes several minutes to execute. Seems the computation of the variable "insert-data" takes time though it is a global variable and computed only once. Should the XSL be addessed or the processor? How can i improve the performance of the XSL.
I'd guess the problem is the generation of text-id, i.e. the expression
generate-id(
$main-root/descendant::text()[
sum((preceding::text(), .)/string-length(.)) ge current-grouping-key()
][1]
)
You are potentially recalculating a lot of sums here. I think the easiest path here would be to invert your approach: recurse across the text nodes in the document, aggregate the string length so far, and output data elements each time a new #index is reached. The following example illustrates the approach. Note that each unique #index and each text node is visited only once.
<xsl:variable name="insert-doc" select="doc($insert-file)"/>
<xsl:variable name="insert-data" as="element(data)*">
<xsl:call-template name="calculate-data"/>
</xsl:variable>
<xsl:key name="index" match="data" use="xsd:integer(#index)"/>
<xsl:template name="calculate-data">
<xsl:param name="text-nodes" select="$main-root//text()"/>
<xsl:param name="previous-lengths" select="0"/>
<xsl:param name="indexes" as="xsd:integer*">
<xsl:perform-sort
select="distinct-values(
$insert-doc/insert-data/data/#index/xsd:integer(.))">
<xsl:sort/>
</xsl:perform-sort>
</xsl:param>
<xsl:if test="$text-nodes">
<xsl:variable name="total-lengths"
select="$previous-lengths + string-length($text-nodes[1])"/>
<xsl:choose>
<xsl:when test="$total-lengths ge number($indexes[1])">
<data
index="{$indexes[1]}"
text-id="{generate-id($text-nodes[1])}">
<xsl:copy-of select="key('index', $indexes[1],
$insert-doc)"/>
</data>
<!-- Recursively move to the next index. -->
<xsl:call-template name="calculate-data">
<xsl:with-param
name="text-nodes"
select="$text-nodes"/>
<xsl:with-param
name="previous-lengths"
select="$previous-lengths"/>
<xsl:with-param
name="indexes"
select="subsequence($indexes, 2)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- Recursively move to the text node. -->
<xsl:call-template name="calculate-data">
<xsl:with-param
name="text-nodes"
select="subsequence($text-nodes, 2)"/>
<xsl:with-param
name="previous-lengths"
select="$total-lengths"/>
<xsl:with-param
name="indexes"
select="$indexes"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>