I am looking to find a way to control both start and end numbering that are added as suffix number to text "period". It seems the logic of my code makes for-each count 3 search matches (index 1-3), and due to using if to find se:Bank it reduces printout to 2 prints.
I know I can use position() -2 to force the numbering to start at 0, but that will only work if I have exact same amount of data. As soon as data growths the position() -2 will assume to step 2 positions minus and will not return zero as start.
I do understand that XSLT does what I actually ask for and returns presumable correct answer.
The interval of number I will be using is between 0 and 3. Most of the time in sequence.
It will be unknown in advance exact interval amounts.
I know there is a start-at using <xsl:number> but it did not solve my problem. Using start-at prints out same number twice.
The JSON file is aligned with a certain standard so I am not allowed to change the structure of the JSON file.
Suspected problem:
The "foreach" spans over more loops than wanted. In this case I need just for the system to loop twice for "se:Bank" and therefor return "period0" and "period1".
Observation:
I suspect that it probably would be better that I learn to extract when xbrl:concept = se:Bank. That would reduce the planned loop down to 2 search, thus being able iterate over them.
Here you find the xsltfiddle.
Below you find same code as in xsltfiddle:
JSON data:
<data>
{
"report": {
"facts": [
{
"xbrl:concept": "se:CompanyName",
"value": "Great Company Ltd"
},
{
"xbrl:concept": "se:Bank",
"numericValue": 1000
},
{
"xbrl:concept": "se:Bank",
"numericValue": 3000
}
]
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xhtml" indent="yes" html-version="5"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)/*"/>
</xsl:template>
<!-- Printout periods -->
<xsl:template match="//*[#key='facts']">
<xsl:for-each select="//*[#key='xbrl:concept']">
<xsl:if test=". = 'se:Bank'">
period<xsl:number value="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result:
<?xml version="1.0" encoding="UTF-8"?>period2period3
Expected result:
<?xml version="1.0" encoding="UTF-8"?>period0period1
If you want to output position() or position() - 1 then <xsl:value-of select="position()"/> or <xsl:value-of select="position() -1 "/>, respectively, suffice, there is no need to feed position() to xsl:number.
Furthermore, I am not sure I understand your requirements correctly, but using a predicate <xsl:for-each select=".//*[#key='xbrl:concept'][. = 'se:Bank']"> instead of the of the nested xsl:if should help to get the result you want, namely to process the two elements in the input sample meeting the condition in the predicate: https://xsltfiddle.liberty-development.net/93wniUS/1
Related
If I have an XSL that creates output like this simple/rough example:
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
... within this same XSL, how can I count how many children the XSL will produce?
You can generate your content into a variable, count the children in the variable, and then emit the content of the variable:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<xsl:variable name="temp-results">
<Parent1>
<ABC><xsl:value-of select="SomeValue1"/></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</Parent1>
</xsl:variable>
<xsl:text>Number of children:</xsl:text>
<xsl:value-of select="count($temp-results/Parent1/*)"/>
<xsl:sequence select="$temp-results"/>
</xsl:template>
</xsl:stylesheet>
One possibility is wrapping the whole output process in a variable and then count its descendants.
So, for example, you can use the following XSLT code
<xsl:template match="/Parent1">
<xsl:variable name="output">
<ABC><xsl:value-of select="SomeValue1"/><ZZZ>Some Grandchild</ZZZ></ABC>
<DEF><xsl:value-of select="SomeValue2"/></DEF>
<GHI><xsl:value-of select="SomeValue3"/></GHI>
...
<YZ><xsl:value-of select="SomeValue9"/></YZ>
</xsl:variable>
<xsl:value-of select="concat('Outputting ', count($output/descendant::*), ' elements.
')" />
<xsl:copy-of select="$output" />
</xsl:template>
Its output is
Outputting 5 elements.
<ABC>
<ZZZ>Some Grandchild</ZZZ>
</ABC>
<DEF/>
<GHI/>
...
<YZ/>
This code accomplishes three things:
First it generates the result and puts it into the variable
It counts all the
children (child::* axis) or
descendants (descendant::* axis) as in the example above
of the elements in the variable
It copies the variable to the output stream
This approach can even be nested - meaning that it can be applied several times, one after another.
Your choices are:
(a) find a way of computing the result as a function of the input
(b) capture the output in a variable and run a second phase of processing against that variable.
(c) a blend of the above: compute some intermediate result in a variable, and use that variable as input to both processes.
In the example you've given, the first approach works perfectly well; but I guess your real problem is more complex than that, otherwise you wouldn't be asking.
I need to count the number of students who have failed an exam or those with some wrong data. This if loop condition is working properly, but the count of the number of students is wrong.
<xsl:if test ="$StudentFinalmark > 100 or $StudentFinalmark < 49">
<xsl:value-of select = "count(StudentFinalmark)"/>
</xsl:if>
For example, if there are four failed students and students with two wrong dates, it's coming as 111111
Please help me with the solution.
Assuming that your xml looked like this (as per your comment below):
<Students>
<StudentResults>
<Marks>
<StudentAssign1>40</StudentAssign1>
<StudentAssign2>40</StudentAssign2>
<StudentExam>40</StudentExam>
</Marks>
</StudentResults>
<StudentResults>
<Marks>
<StudentAssign1>1</StudentAssign1>
<StudentAssign2>2</StudentAssign2>
<StudentExam>3</StudentExam>
</Marks>
</StudentResults>
<StudentResults>
<Marks>
<StudentAssign1>33</StudentAssign1>
<StudentAssign2>33</StudentAssign2>
<StudentExam>33</StudentExam>
</Marks>
</StudentResults>
</Students>
an XSLT like this will return the results you are after:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="Output">
<xsl:value-of select="count(Students/StudentResults[sum(Marks/*) > 100 or sum(Marks/*) < 49])" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
This returns the result:
<Output>2</Output>
It returns two because, in the example XML above, the first StudentResults has a sum of 120 (which fails because it is bigger than 100) and the second has a sum of 6 (which fails because it is less than 49)
The trick is to output a single item which is a count of the elements which you select via xpath, not via an "if" and "for" combination. There isn't a need to use a variable either.
I have a fairly convoluted XML file and I need to do a weighted average of a few values within it using XSL. I am able to complete a sum of the weights OR of the values, but I cannot get the multiplication to work. I get an error:
XPTY0004: A sequence of more than one item is not allowed as the first
operand of '*'
I am not able to share the XML, but I have simplified the XML to the following example (assume there are a large number of foos):
<group>
<fooList>
<foo>
<attributeList>
<Attribute ID="1" Weight="0.5">
<otherParams />
</Attribute>
</attributeList>
<Properties>
<PhysicalProperties>
<Volume Average="125" Unknown="50" />
</PhysicalProperties>
</Properties>
</foo>
</fooList>
</group>
My current attempt to get the weighted average is the following:
<xsl:variable name="WeightedVolume" select="sum(/group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/attributeList/Attribute/#Weight * /group/fooList/foo[attributeList/Attribute/[#ID=$test_id]]/Properties/PhysicalProperties/Volume/#Average)"/>
I know there are similar questions available - but most of them deal with something like summing and multiplying foo
<foo>
<Weight>0.5</Weight>
<VolumeAverage>125</VolumeAverage>
</foo>
The answer on this StackOverflow Question appeals to me, but I cannot seem to make it work.
I'm using Saxon-HE 9.5.1.1N from Saxonica, with Visual Studio 2013.
Edited
I was able to get something to work for XSL 2, but need to have a fall-back for XSL1.
<xsl:variable name="WeightedVolume" select="sum(for $i in /group/FooList/foo[attributeList/Attribute[#ID=$test_id] return $i/AttributeList/Attribute/#Weight * $i/Properties/PhysicalProperties/Volume/#Average)"/>
To follow the example in that question you linked to, you would use this in XSLT 2.0/XPath 2.0:
<xsl:variable name="FoosToCalculate"
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]" />
<xsl:variable name="WeightedVolume"
select="sum($FoosToCalculate/(attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average)
)"/>
Doing this summing in XSLT 1.0 is considerably more involved and typically involves either using recursive templates or some manifestation of the node-set() function. Here is an example of the latter:
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<!-- determine $test_id however you need to -->
<xsl:variable name="products">
<xsl:for-each
select="/group/fooList/foo[attributeList/Attribute/#ID = $test_id]">
<product>
<xsl:value-of select="attributeList/Attribute/#Weight *
Properties/PhysicalProperties/Volume/#Average" />
</product>
</xsl:for-each>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>
For completeness, if you want to sum over a computed quantity in XSLT 1.0, there are three ways of doing it:
(a) recursion: write a recursive template that processes the items in the sequence one by one, computing the total as it goes.
(b) create an XML tree in which the computed quantities are node values, and then process this tree using the sum() function. To do this in a single stylesheet you will need the exslt:node-set() extension function.
(c) use an extension function provided by the XSLT vendor, or user-written using the facilities provided by the vendor for calling external functions.
In XSLT 2.0, it can always be done using the construct
sum(for $x in node-set return f($x))
where f is a function that computes the quantity.
I am a novice XSLT developer. I have been asked to fix an issue on a project where the original developer is no longer with us. In the XSLT, there is a for-each loop using a key and a count
<xsl:for-each select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
...
This is the key:
<xsl:key name="subsat" match="ns0:Parts/ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01" />
In the XML file being transformed, there are two sibling nodes that represent sub-parts:
<ns0:BOM referentId="10000:65091335:65359080">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_1</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
<ns0:BOM referentId="10000:65102551:86713230">
<ns0:BomText01>3069260-303-SUB0027</ns0:BomText01>
<ns0:ItemNumber>My_part_2</ns0:ItemNumber>
<ns0:ItemType>Part</ns0:ItemType>
<ns0:Qty>67</ns0:Qty>
</ns0:BOM>
However, the loop is only picking up the first node (My_part_1). I suspect it's because of the count=1 but I really don't know. And I don't know how to modify it. Ideas? If I need to include more data, let me know.
Assuming that the relevant part of your XSLT looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="ns0" version="1.0">
<xsl:key name="subsat" match="ns0:BOM[ns0:FindNum!='0']" use="ns0:BomText01"/>
<xsl:template match="ns0:Parts">
<xsl:for-each
select="ns0:BOM[count(. | key('subsat', ns0:BomText01)[1]) = 1][ns0:BomText01]">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It will only print the first of the elements because it is selecting the BOM elements which have an unique BomText01 value. That's the expected result.
If the BomText01 is an ID field (as it seems it is) and you expected to get both result (perhaps, because their ItemNumber contains different values), the error is possibly in your source (which assigned equal IDs when it should not have done so).
If you change one of those values in the source, you should be able to select both and verify this.
Is there any possibility to check if it exists a duplicate node in an xml file, then check the contains of this node and if possible differenciate the both node by an Id number using xslt?
I have for example this xml file
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>...........<el3>
</Racine>
In this case for example, the node "fils1" appears twice in the node "el1" and the contains of each of them is the same. I need in this case to transform my xml file so that this node will become
<fils1 id=1>context1</fils1>.
I add automatically an Id-number to differenciate the both node with the same name and same contain. And when the node got the same name with different contains( also a space in the contain of the node has to be considered as a difference), then lets the node as they are.
How can i make this possible using XSLT? Could someone here help me to transform that?
Thanks a lot for your help.
Franky
Thanks for yours remarks. Here is the expected output:
<Racine>
<el1>
<fils1 id=1>context1</fils1>
<fils2>test1</fils2>
<fils1 id=2>context1</fils1>
</el1>
<el2>
<fils1 id=1>context2</fils1>
<fils2>test2</fils2>
<fils1 id=2>context2</fils1>
</el2>
<el3>...........
<fils1 id=3>context1</fils1>
<el3>
</Racine>
Conerning the node "fils2" in "el1" and "el2", the contain is different, then i want to keep them unchange and when not, i want to add an id number to make the difference.
This XSLT 1.0 style-sheet...
<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:strip-space elements="*" />
<xsl:key name="kCommon" match="*[starts-with(name(),'fils')]"
use="concat(name(),'|',.)" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[starts-with(name(),'fils')]
[count(key('kCommon',concat(name(),'|',.))) >= 2]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:variable name="this-key" select="concat(name(),'|',.)" />
<xsl:attribute name="id"><xsl:value-of select="
count(preceding::*
[starts-with(name(),'fils')]
[count(.|key('kCommon',$this-key)) =
count( key('kCommon',$this-key)) ]
) + 1" /></xsl:attribute>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
...will transform this input...
<Racine>
<el1>
<fils1>context1</fils1>
<fils2>test1</fils2>
<fils1>context1</fils1>
</el1>
<el2>
<fils1>context2</fils1>
<fils2>test2</fils2>
<fils1>context2</fils1>
</el2>
<el3>
<fils1>context1</fils1>
</el3>
</Racine>
...into...
<Racine>
<el1>
<fils1 id="1">context1</fils1>
<fils2>test1</fils2>
<fils1 id="2">context1</fils1>
</el1>
<el2>
<fils1 id="1">context2</fils1>
<fils2>test2</fils2>
<fils1 id="2">context2</fils1>
</el2>
<el3>
<fils1 id="3">context1</fils1>
</el3>
</Racine>
Explanation
We build a key ('kCommon') of the fils elements that have both common name and common text content. We look for elements which have membership in thier key group of at least two - in other words filX type elements that have a name and text in common with at least one other filX element. This is our last template.
For each such common element, we build two sets:
The set of all preceding fil elements in the common group. This set is given by...
Set 1
preceding::*[starts-with(name(),'fils')]
And this node's common name+content group, which is...
Set 2
key('kCommon',concat(name(),'|',.))
But because we will reference concat(name(),'|',.) quiet a few times, and in places where we don't have direct access to this context node, we just compute the concat() and substitute into Set 2 like this. Set 2 is now quick to compute.
Set 2
key('kCommon',$this-key)
And then we take a set intersection using the Kaysian method.
$set1[count(.|$set2)=count($set2)]
This intersection is the set of all like-minded element to this one that preceded it. We just count them up and add one, and this gives us an ordinal for the id property.