Duplicate rows showing up as a result of Muenchian grouping in XSLT - xslt

I have an XSLT transformation that's returning a lot of duplicate items, as seen here:
after the snip, the second category shows up at the bottom:
This is what it's supposed to look like:
The way this report is structured is:
Project
> Phases (formatted with light grey background)
> Categories (if any, formatted with light blue background)
> Deliverables (formatted with white background)
> Sub-deliverables
The hierarchy works, but there's just too much. I'm not really sure what's going on at this stage. I'm using the Muenchian Method in XSLT 1.0.
Here is my input: http://pastebin.com/uJw1sPEQ
Here is my transformation: http://pastebin.com/7cJd6bj5

Only thing that looks strange is this line:
<xsl:apply-templates select="//Row[generate-id() = generate-id(key('Project-Phases', concat(ProjectNo, '|', Phase))[1][ProjectNo=current()/ProjectNo])]" mode="phase"/>
The [ProjectNo=current()/ProjectNo] part should not be attached to the key. It should instead be moved after the last ].

Related

Refer to specific cell in xslt import/export filter for Calc

I am using xslt filter for importing/exporting data from Calc worksheet. Is it possible to refer to a specific cell address ? For example, if we want to export data from cell B2, how do we refer to this cell address in export xslt ?
Without knowing much about Openoffice or their xslt filter function, I can tell you that you're probably going to need a fairly simple XPath to reference a specific Cell's data - I doubt it would be as simple as calling getCell('B2') unless they have provided you with some custom xslt functions (I'm assuming they've put you in a raw XSLT environment).
Anyway, I think this question may be more about XSLT and xpath, than it is about openoffice. With that in mind, I'm going to fashion my own sample xml and examples and hopefully that will be enough to get you started.
For an input xml that looks something like this:
<ooo_calc_export>
<ooo_sheet num="1" name="sheet1">
<ooo_row num="2">
<fisrtCell>Oh</firstCell>
<secondCell>Hai</secondCell>
<thirdCell>There</thirdCell>
</ooo_row>
<ooo_row num="3">
<fisrtCell>Oh</firstCell>
<secondCell>Hello</secondCell>
<thirdCell>Back!</thirdCell>
</ooo_row>
</ooo_sheet>
</ooo_calc_export>
An absolute XPath to access cell B2's data would look like this ooo_calc_export/ooo_sheet/ooo_row[#num='2']/secondCell/text()
But the above is an absolute path and in XSLT, we would often author relative xpaths as we are in the midst of processing a document. Imagine you're in a template which matches on the ooo_calc_export node and you wanted to store Cell B2's data in a variable for later use. Consider this example:
<xsl:template match="/ooo_calc_export">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2" select="ooo_sheet/ooo_row[#num='2']/secondCell/text()" />
</xsl:template>
Now lets imagine you wanted a template to match on the cell B2 node itself:
<xsl:template match="ooo_row[#num='2']/secondCell">
<!-- a relative xpath does not being with a forward slash -->
<xsl:variable name="B2_text" select="text()" />
</xsl:template>
This is a good tutorial on XSLT to get you started. Also, the W3 Schools references on XPath and XSLT aren't the worst.

XSLT1 Select Sibling's Child Node Values

I have an XML document like this:
(p is defined previously)
<p:Players>
<p:Player>
<p:Name>David</p:Name>
<p:Club>
<p:Name>Madrid</p:Name>
</p:Club>
<p:PreviousClubs>
<p:Club><p:Name>Milan</p:Name></p:Club>
<p:Club><p:Name>Manchester</p:Name></p:Club>
</p:PreviousClubs>
</p:Player>
<p:Player>
<p:Name>Alex</p:Name>
<p:Club>
<p:Name>Madrid</p:Name>
</p:Club>
<p:PreviousClubs>
<p:Club><p:Name>Birmingham</p:Name></p:Club>
<p:Club><p:Name>Manchester</p:Name></p:Club>
</p:PreviousClubs>
</p:Player>
<p:Player>
<p:Name>Fred</p:Name>
<p:Club>
<p:Name>Madrid</p:Name>
</p:Club>
<p:PreviousClubs>
<p:Club><p:Name>Milan</p:Name></p:Club>
<p:Club><p:Name>Birmingham</p:Name></p:Club>
</p:PreviousClubs>
</p:Player>
</p:Players>
I'd like to get the Names of all the players who've previously played for a given club.
This is what I have so far, but it isn't picking anything up:
/*[1]/p:Player[p:PreviousClubs/p:Club/p:Name='Manchester']/p:Name/text()
I expect that to return
David
Alex
But I get nothing
Can someone see where I'm going wrong?
The namespace and its prefix of p: is correct - have used elsewhere and its fine. I feel my logic around selecting the particular parent node is wrong...
I need to stick to XSLT 1.0 as its BizTalk driven.
This is almost certainly an issue with namespaces.
In your XML, you are using a namespace prefix, but there is not a corresponding definition for the namespace. You should really have a definition such as this on the root element
<p:Players xmlns:p="mynamespace">
Then, within your XSLT, you will also need to ensure the same namespace URI is defined.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:p="mynamespace">
Note that the prefix p doesn't have to be the same. It is the namespace URI that is the important identifier here. If the URI doesn't match what is in the XML, it won't find the elements if you use the prefix.
If you therefore use this XSLT, it should return David and Alex as expected (although you would need to add extra code if you wanted line breaks here)
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:p="mynamespace">
<xsl:template match="/">
<xsl:apply-templates
select="/*[1]/p:Player[p:PreviousClubs/p:Club/p:Name='Manchester']/p:Name/text()" />
</xsl:template>
</xsl:stylesheet>
All this means is your original XPath expression is correct! However, you can simplify slightly as there is no need for the [1] at the start. The first / matches the document element, and so /* will match the root element, and because you can only have one root element in well-formed XML, there is no need to qualify it with the index [1]
/*/p:Player[p:PreviousClubs/p:Club/p:Name='Manchester']/p:Name/text()
You could also drop the text() at the end, because the built-in templates will output the text for an element in this case anyway.

xsl - select node by child

I have a problem selecting just elements of an xml, which contain a specific child node. Asumme the following part of an xml:
<root>
<Navision.Buchungen>
<Saldo>-110867.7500</Saldo>
<Navision.Kontostruktur>
<Bereich>1</Bereich>
</Navision.Kontostruktur>
</Navision.Buchungen>
<Navision.Buchungen>
<Saldo>-3082585.2100</Saldo>
<Navision.Kontostruktur>
<Bereich>2</Bereich>
</Navision.Kontostruktur>
</Navision.Buchungen>
...
</root>
Now I have an xsl part like this to get the sum of 'Saldo':
<xsl:variable name="FACT0" select="sum(//root/Navision.Buchungen/Saldo)"/>
But how can I select just the Saldo for 'Bereich' 1 for example?
Use this XPath:
//root/Navision.Buchungen[Navision.Kontostruktur/Bereich = 1]/Saldo
//root/Navision.Buchungen[Navision.Kontostruktur/Bereich = 1]/Saldo
Edited:
oh already posted.
For further problems you can use one of the online testbeds like this one. And of course good manuals like those from w3schools, also with testbeds for xsl

XSLT 1.0 adding values together from multiple nodes

Say I have the following XML
<A>100</A>
<B>200</B>
<C>300</C>
and the following XSLT
<TEST>
<xsl:value-of select="A + B + C"/>
</TEST>
It produces the following output
<TEST>600</TEST>
however, when one of the nodes is blank
<A></A>
<B>200</B>
<C>300</C>
I get the following.
<TEST>NaN</TEST>
I only want to add the nodes that are valid numbers. I could do this if xsl allowed me to dynamically replace a variable value by adding to the already existing variable, but even that would be messy. I assume there is an easy way that I'm missing?
I want XSLT 1.0 answers only please.
Thanks!
<TEST>
<xsl:value-of select="sum((A | B | C)[number(.) = .])"/>
</TEST>
That is, sum the subset of the elements A,B,C consisting of those whose contents can be used as numbers.
Note that number('') yields NaN, and (NaN = NaN) is false, so this will not include elements without text content.
We test for numbers as discussed at https://stackoverflow.com/a/3854389/423105
A little variation of LarsH's answer:
sum((A|B|C)[number(.) = number(.)])
the expression in the predicate doesn't cause any type error in XPath 2.0, because both arguments of = are of the same type -- xs:double.
There is also:
For XSLT 1.0:
sum((A|B|C)[string(number())!='NaN'])
For XSLT 2.0:
sum((A,B,C)[string(number())!='NaN'])
For interest, here is another one. It works for both XSLT 1.0 and 2.0
sum((A|B|C)[number()>-99999999])
In the above, replace -99999999 with one less than the lower bound of the possible values. This works because NaN > any-thing always returns false. This is probably the most efficient solution yet, but it may be seen as a little ugly.
For example, if you know for a fact that none of A, B or C will be negative values, you could put:
sum((A|B|C)[number()>0])
...which looks a little cleaner and is still quiet readable.
<xsl:value-of select="sum(/root/*[self::A or self::B or self::C][.!=''])"/>
This will add values from A, B, and C under the "Root" element so long as the value isn't blank.

how to use two conditions in select conditions in xslt when using Apply template

<xsl:apply-templates mode="block2sequence" select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] and NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
why cant i use two conditions in above select condition, can any one suggest me
<xsl:apply-templates mode="block2"
select="NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1] "/>
why cant i use two conditions in above select condition
I guess this is to mean, "why can't the two conditions be specified in the same predicate?"
The answer is that the expression:
NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId and position() = 1]
isn't equivalent at all to the 1st expression above.
The first expression selects the first Table child of NewDataSet such that the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case we don't know which child (at which position) of NewDataSet will be selected -- any child that happens to be the first with the specified properties, will be selected.
On the other side, the latter expression selects the first Table child of NewDataSet only if the string value of its CTD_CTD_PKG_ID child is equal to the string value of $PackageId. In this case, if anything is selected, it would be the first Table child.
If you want an equivalent expression to the first one, that has only one predicate, one such expression is:
NewDataSet/Table
[CTD_CTD_PKG_ID =$PackageId
and
not(preceding-sibling::Table[CTD_CTD_PKG_ID =$PackageId ])
]
Update: The OP has published a code snippet:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][position()=1]
and
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][position()=1]"/>
This code will cause an error thrown at compile time by the XSLT processor.
The value of the select attribute is a boolean (expr1 and expr2), however templates in XSLT 1.0 and XSLT 2.0 can only be applied on nodes. A boolean isn't a node -- hence the error.
Solution:
My first guess is that you want templates to be applied on both nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId][1]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType][1]"/>
My second guess is that you want templates applied only on the first of the two nodes. If this is so, then use:
<xsl:apply-templates mode="block2sequence" select=
"(NewDataSet/Table[CTD_CTD_PKG_ID =$PackageId]
|
NewDataSet/Table[CTD_SEQ_NUM =$strXSLMsgType]
)
[1]
"/>
Notes:
Please, learn how to ask a question -- provide all relevant data and explain -- in the question, not in subsequent comments.
Did you know that [1] is equivalent to [position()=1] and is shorter?
You can use two conditions and your expression looks perfectly correct. If it is failing with an error, please tell us the error. If it is not selecting what you want, then (a) show us your source document, and (b) tell us what you want to be selected.
(You know, your question gives so little information, you don't give the impression that you really want an answer.)