Display a value from a preceding-sibling of a following sibling/child node? - xslt

Based on this XML I am trying to display a table with a row for each DOCREF that also shows the STEP2/TITLE where the REFERENCE equals the ID. I can make this work where the DOCREF/#REFERENCE = STEP2/#ID but where I am running into troubles is that it has been requested that the STEP2/TITLE also show for each STEP3 element until there is a new STEP2 element, and then it would show for all the STEP3's until it changes again and so on.
<WORKCARD>
<STSBODY>
<DOCREFS>
<DOCREF REFERENCE="123" VALUE="Ref1"/>
<DOCREF REFERENCE="456" VALUE="Ref2"/>
<DOCREF REFERENCE="789" VALUE="Ref3"/>
</DOCREFS>
</STSBODY>
<BODY>
<ITEMS>
<ITEM>
<XML>
<STEP2 ID="123">
<TITLE>Test1</TITLE>
</STEP2>
</XML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP3 ID=456>Step info goes here</STEP3>
</XML>
</ITEMXML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP2 ID=789>Test2</STEP3>
</XML>
</ITEMXML>
</ITEM>
</ITEMS>
</BODY>
</WORKCARD>
I am modifying an existing XSLT. Here is the section I am working with and you can see my various attempts that don't get me what I need.
<xsl:key name="step2Ref" match="STEP2" use="#ID" />
<xsl:when test="DOCREF[#TASK_CARD_ITEM > $item]">
<xsl:for-each select="DOCREF[#TASK_CARD_ITEM > $item and not($doctype = 'NDT' and key('refItem', #TASK_CARD_ITEM)/descendant::L1ITEM[#ID] and not(key('refItem',#TASK_CARD_ITEM)/descendant::L2ITEM))]">
<xsl:sort select="#TASK_CARD_ITEM" data-type="number" />
<!--<xsl:call-template name="subtaskitemrow"/>-->
<fo:table-row>
<fo:table-cell number-columns-spanned="6" border="solid 1pt red">
<fo:block>
<!-- this displays the correct info when there is a matching STEP2/#ID-->
<xsl:value-of select="key('step2Ref', #REFERENCE)/TITLE" />***
<!--This gets the STEP2/TITLE where STEP2/#ID = #REFERENCE-->
<xsl:value-of select="//STEP2/#ID" />+++
<xsl:value-of select="//ITEM/#TASK_CARD_ITEM" />***
<xsl:value-of select="#TASK_CARD_ITEM"/>+++
<xsl:variable name="tcitem"><xsl:value-of select="#TASK_CARD_ITEM" /></xsl:variable>
<xsl:value-of select="//ITEM[$tcitem]/#TASK_CARD_ITEM" />***
<!--preceding-sibling STEP2/#ID-->
</fo:block>
</fo:table-cell>
</fo:table-row>
</xsl:for-each>
</xsl:when>
Some notes I've written to myself are:
When looping through the DOCREFS I need to display the STEP2/TITLE for each one until the STEP2/TITLE changes and then display the new one.
I need to match STEP3's preceding-sibling STEP2's #ID
Please help?
ETA:
Desired output for this sample (changed text in xml slightly from original post) would be something like this:
Test1
Test1
Test2
So for the first and second DOCREFs it would show the title from the first STEP2 and the third DOCREF would show the title from the second STEP2.

Your XML document is not well-formed, and - more importantly - the second <STEP2> has no <TITLE>. Assuming it can be corrected to:
<WORKCARD>
<STSBODY>
<DOCREFS>
<DOCREF REFERENCE="123" VALUE="Ref1"/>
<DOCREF REFERENCE="456" VALUE="Ref2"/>
<DOCREF REFERENCE="789" VALUE="Ref3"/>
</DOCREFS>
</STSBODY>
<BODY>
<ITEMS>
<ITEM>
<XML>
<STEP2 ID="123">
<TITLE>Test1</TITLE>
</STEP2>
</XML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP3 ID="456">Step info goes here</STEP3>
</XML>
</ITEMXML>
</ITEM>
<ITEM>
<ITEMXML>
<XML>
<STEP2 ID="789">
<TITLE>Test2</TITLE>
</STEP2>
</XML>
</ITEMXML>
</ITEM>
</ITEMS>
</BODY>
</WORKCARD>
you can use the following logic to retrieve the data you need (for testing purposes, more is shown than required in your question):
<xsl:key name="step2Ref" match="STEP2" use="#ID" />
...
<xsl:template match="DOCREF">
<tr>
<!-- DOCREF VALUE-->
<td><xsl:value-of select="#VALUE" /></td>
<!-- DOCREF REFERENCE -->
<td><xsl:value-of select="#REFERENCE" /></td>
<xsl:variable name="ref">
<xsl:choose>
<xsl:when test="key('step2Ref', #REFERENCE)">
<xsl:value-of select="#REFERENCE" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="preceding-sibling::DOCREF[key('step2Ref', #REFERENCE)][1]/#REFERENCE" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- REFERENCE USED FOR KEY -->
<td><xsl:value-of select="$ref" /></td>
<!-- RETRIEVED TITLE -->
<td><xsl:value-of select="key('step2Ref', $ref)/TITLE" /></td>
</tr>
</xsl:template>
In the above example, the following result is returned:
Ref1 123 123 Test1
Ref2 456 123 Test1
Ref3 789 789 Test2
Note that it is assumed that the first <DOCREF> does have a related <STEP2>.

Related

Getting the value of a parent node element in XSL

Unfortunately the xsl outputs the same ShortName entry for each section with this construct:
<xsl:for-each select="Questionnaire/Section">
<xsl:for-each select="Item">
<xsl:value-of select="Question"/></strong>
<xsl:for-each select="Optionlist/option">
<input type="radio" >
<xsl:attribute name="name"><xsl:value-of select="//ShortName" />
</xsl:attribute>
The XML:
<Questionnaire>
<Section><Title>Available Data</Title>
<Item>
<ShortName>Name</ShortName>
<Optionlist>
<option><entry>Yes</entry></option></Optionlist>
</Item>
</Section>
Thanks
It seems instead of the path //ShortName you want ancestor::Item/ShortName.

find items from current position containing specific nodes

I need to parse an xml consisting of components inside nested groups. The result will be js array. I want the group to be created only if has a component in its nested levels.
I can search if a node has components with below method:
<xsl:if test="count(.//ModuleComponent) > 0">
and I need to put comma between the array elements only if the other all nodes from current position has components. And this is what I can't do. I need to search if inside all nodes from current if they have components.
this line works only in the next sibling, not all of them.
<xsl:if test="count(following-sibling::ModuleComponent) > 0">
my sample is:
<xsl:text>[</xsl:text>
<!--create components of group-->
<xsl:for-each select="Module/Groups/Group">
<xsl:call-template name="GroupTemplate">
</xsl:call-template>
</xsl:for-each>
<xsl:text>]</xsl:text>
<xsl:template name="GroupTemplate">
<xsl:variable name="groupID" select="#ID"/>
<!-- create this group only if it has components deep inside -->
<xsl:if test="count(.//ModuleComponent) > 0">
{
id: <xsl:value-of select="$ID" />
...
group body
...
}
<xsl:if test="count(following-sibling::ModuleComponent) > 0"><xsl:text>,</xsl:text></xsl:if>
</xsl:if>
</xsl:template>
and sample xml is:
<Module>
<Groups>
<Group ID="1">
<Groups>
<Group ID="11">
<Component ID="c1"/>
</Group>
</Groups>
</Group>
<Group ID="2">
<Groups>
<Group ID="22">
</Group>
</Groups>
</Group>
<Group ID="3">
<Groups>
<Group ID="33">
<Component ID="c3"/>
</Group>
</Groups>
</Group>
</Groups>
</Module>
any ideas or help is appreciated.
Make it a matching template instead of a called one, and move the "do I contain any ModuleComponent" logic into a predicate instead of an if:
<xsl:text>[</xsl:text>
<xsl:apply-templates select="Module/Groups/Group[.//ModuleComponent]"/>
<xsl:text>]</xsl:text>
<xsl:template match="Group">
<xsl:if test="position() > 1">,</xsl:if>
{
id: <xsl:value-of select="#id" />
...
group body
...
}
</xsl:template>
Now you're operating only on the groups that have at least one component, and position() is the position of this Group within that list rather than within the list of all groups, so you simply add a comma before all but the first one.
If you want to generate an array, you don't have to call a separate template. You could change your first for-each to iterate on .//Component and test for the last() position:
<xsl:text>[</xsl:text>
<xsl:for-each select=".//Component">
{
id: <xsl:value-of select="#ID" />
...
component body
...
}
<xsl:if test="not(position() = last())"><xsl:text>,</xsl:text></xsl:if>
</xsl:for-each>
<xsl:text>]</xsl:text>

in xslt how to compare a string value with another variable containing multiple values

I have the following xml file. I need to fetch all unique "owner" values from this and perform some operations.
<issues>
<issue>
<owner>12345</owner>
</issue>
<issue>
<owner>87654</owner>
</issue>
<issue>
<owner>12345</owner>
</issue>
</issues>
<tests>
<test>
<owner>34598</owner>
</test>
<test>
<owner>12345</owner>
</test>
<test>
<owner>34598</owner>
</test>
<test>
<owner>11111</owner>
</test>
</tests>
I tried using following xslt script.
<xsl:for-each select="issues/issue[not(child::owner=preceding- sibling::issue/owner)]/owner">
<!--some code-->
</xsl:for-each>
<xsl:for-each select="tests/test[not(child::owner=preceding- sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner">
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<!--some code-->
</xsl:if>
</xsl:for-each>
But am getting duplicate values. Could anyone please help me with this?
In XSLT 2.0 you can just use for-each-group:
<xsl:for-each-group select="issues/issue | tests/test" group-by="owner">
<!-- in here, . is the first issue/test with a given owner and current-group()
is the sequence of all issue/test elements that share the same owner -->
</xsl:for-each-group>
If you are stuck on 1.0 then you need to use a technique called "Muenchian grouping" - define a key that groups elements with the same owner, then process just the first item in each group using a generate-id trick
<xsl:key name="ownerKey" match="issue | test" use="owner" />
<xsl:for-each select="(issues/issue | tests/test)[generate-id()
= generate-id(key('ownerKey', owner)[1])]">
<!-- one iteration per unique owner, with . being the parent element of the
first occurrence -->
</xsl:for-each>
But am getting duplicate values.
It's not quite clear where you are getting the duplicate values - since your posted code does not output anything. If you had tested something like:
...
<xsl:for-each select="issues/issue[not(child::owner=preceding-sibling::issue/owner)]/owner">
<out>
<xsl:value-of select="." />
</out>
</xsl:for-each>
....
you would have seen that it does work (albeit inefficiently) and returns:
...
<out>12345</out>
<out>87654</out>
...
Similarly, testing the following snippet:
...
<xsl:for-each select="tests/test[not(child::owner=preceding-sibling::test/owner)]/owner">
<xsl:variable name="IrmAs">
<xsl:value-of select="." />
</xsl:variable>
<xsl:variable name="IssueList">
<xsl:value-of select="//issues/issue/owner"/>
</xsl:variable>
<xsl:if test="not(contains($IssueList,$IrmAs))">
<out>
<xsl:value-of select="." />
</out>
</xsl:if>
</xsl:for-each>
...
produces:
...
<out>34598</out>
<out>11111</out>
...
So the problem must be in the part of the code that you haven't posted. Note also that the code you did post has several syntax errors, e.g. :
<xsl:value-of select="//issues/issue/owner">
needs to be:
<xsl:value-of select="//issues/issue/owner"/>

<xsl:number> reset numbering

I have an XML file with a list of items with two different qualities and I need to create an HTML output that list the items in the two categories with a numbering sequence that start with a on both. I cannot find a solution. Here are the files I created so far:
XML
<?xml version="1.0" encoding="UTF-8"?>
<refrigerator>
<item>
<quality>Good</quality>
<item_name>eggs</item_name>
</item>
<item>
<quality>Good</quality>
<item_name>chess</item_name>
</item>
<item>
<quality>Good</quality>
<item_name>soda</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>chicken meat</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>spinach</item_name>
</item>
<item>
<quality>Bad</quality>
<item_name>potatoes</item_name>
</item>
</refrigerator>
XSL
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator/strong>
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Good'">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name"/>
</xsl:if>
</xsl:for-each>
, <strong>and these are the bad ones/strong>
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Bad'">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name"/>
</xsl:if>
</xsl:for-each>
. Some more text over here.</td>
</tr>
</table>
HTML
These are the good items in the refrigerator:a) eggs b) chess c) soda , and these are the bad ones:d) chicken meat e) spinach f) potatoes . Some more text over here.
OUTPUT needed
These are the good items in the refrigerator:a) eggs b) chess c) soda , and these are the bad ones:a) chicken meat b) spinach c) potatoes . Some more text over here.
Any help is greatly appreciate it.
Regards.
A.
Your problem is that position() is sensitive to exactly what list of nodes you're currently for-eaching over. Instead of
<xsl:for-each select="refrigerator/item">
<xsl:if test="quality = 'Good'">
put the test in the for-each select expression
<xsl:for-each select="refrigerator/item[quality = 'Good']">
and similarly for the "Bad" case.
As Tomalak suggests you can save repeating the same code in the two cases by moving it to a separate template and using apply-templates instead of for-each.
Either: Use <xsl:for-each> correctly.
<xsl:template match="refrigerator">
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator</strong>
<xsl:for-each select="item[quality = 'Good']">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</xsl:for-each>
<xsl:text>, <xsl:text>
<strong>and these are the bad ones</strong>
<xsl:for-each select="item[quality = 'Bad']">
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</xsl:for-each>
<xsl:text>. Some more text over here.</xsl:text>
</td>
</tr>
</table>
</xsl:template>
Or, don't repeat yourself and don't use <xsl:for-each> at all.
<xsl:template match="refrigerator">
<table width="100%" border="1">
<tr>
<td>
<strong>These are the good items in the refrigerator</strong>
<xsl:apply-templates select="item[quality = 'Good']" mode="numbered" />
<xsl:text>, <xsl:text>
<strong>and these are the bad ones</strong>
<xsl:apply-templates select="item[quality = 'Bad']" mode="numbered" />
<xsl:text>. Some more text over here.</xsl:text>
</td>
</tr>
</table>
</xsl:template>
<xsl:template match="item" mode="numbered">
<div>
<strong><xsl:number format="a) " value="position()"/></strong>
<xsl:value-of select="item_name" />
</div>
</xsl:template>
Or, and this is even more preferred, use HTML numbered lists. Output <ol> and <li> and style them via CSS, instead of hard-coding list numbers in your output.

for-each doesn't work for my xslt

I have a problem I couldn't solve despite extensive searching...
This is a part from an XML.
<contributors>
<authors>
<author>Willett, C G</author>
<author>Tepper, J E</author>
<author>Kaufman, D S</author>
<author>Shellito, P C</author>
<author>Eliseo, R</author>
<author>Convery, K</author>
<author>Wood, W C</author>
</authors>
</contributors>
I tried to import all authors into a Filemaker cell by using this xsl (excerpt)
<FIELD EMPTYOK="YES" MAXREPEAT="15" NAME="Author" TYPE="TEXT"/>
<COL>
<DATA>
<xsl:for-each select="contributors/authors">
<xsl:value-of select="author">
</xsl:value-of>
</xsl:for-each>
</DATA>
</COL>
Unfortunately only the first name is imported. Why? What's missing?
Would be glad if someone could help me...
Cheers
You need to iterate through the authors, not through authors, because there's only one authors, but many authors:
<xsl:for-each select="contributors/authors/author">
<xsl:value-of select="concat(., ' ')" />
</xsl:for-each>
<FIELD EMPTYOK="YES" MAXREPEAT="15" NAME="Author" TYPE="TEXT"/>
<COL>
<DATA>
<xsl:for-each select="contributors/authors/author">
<xsl:value-of select="."/>
<xsl:if test="position() < last()">; </xsl:if>
</xsl:for-each>
</DATA>
</COL>
This would produce a semicolon-separated list of all the author values. The <xsl:if> is there to avoid adding a trailing semicolon after the last author name.