XSL two for-each loops for same node - xslt

Problem I have is I want to loop round the parents making them bold then get the children via the id:pid (parent id) and list them. My second loop doesn't work.
XML
XSL
<xsl:choose>
<xsl:when test="#PARENT_OBH_ID">
<b><xsl:value-of select="#TITLE"/></b>
<xsl:for-each select="FOOTER">
-<xsl:value-of select="#TITLE"/>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:for-each>
Thanks

You're probably better off restructuring this to use templates, the system you're using at the moment means that the context data is becoming confused (you're xslt parser isn't sure which element it should read attributes from inside the second loop)
<xsl:choose>
<xsl:when test="#PARENT_OBH_ID">
<b><xsl:value-of select="#TITLE"/></b>
<xsl:apply-templates select="FOOTER" />
</xsl:when>
</xsl:choose>
<xsl:template match="FOOTER">
<xsl:value-of select="#TITLE"/>
</xsl:template>
apply-templates restarts the context with the footer element as the main focus (so #TITLE refers to the title attribute on footer, which is what you were aiming for I am guessing?)

Related

Working on Variable in XSL loop to hold dynamic value

Working on the xsl and i am looking for variable inside a loop to reset on new record.
Fetching records from oracle table
<xsl:variable name="curr_temp_emp_no" select="'##'"/>
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != $curr_temp_emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
I am trying to compare variable "curr_temp_emp_no" if new value only it prints "emp_no - variable_desc" otherwise(if same emp_no) then print only "variable_desc".
I understood from google that Variables in XSLT are immutable, once we assign them a value, we can't change them.
Refence: Can you simulate a boolean flag in XSLT?
Can anyone please help me over here in writting this logic.
It looks like you want to compare the last two values. I could achieve this by:
<xsl:template match="/">
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="/data/test/loof/super_incompleted"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="helperTemplate">
<xsl:param name="nodes"/>
<xsl:param name="oldVal" select="''"/>
<xsl:if test="$nodes">
<xsl:variable name="curr_temp_emp_no" select="$nodes[1]/emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no != $oldVal">
<xsl:value-of select="$curr_temp_emp_no"/>
<fo:inline> - </fo:inline>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$nodes[1]/variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="helperTemplate">
<xsl:with-param name="nodes" select="$nodes[position() > 1]"/>
<xsl:with-param name="oldVal" select="$nodes[1]/emp_no"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
If you want to have the comparison on all values(if there was no previous element with that content), you can sort them first and use the same code.
You're asking us to reverse engineer your requirements from non-working code, which is always a challenge, but you seem to be carrying over ideas from procedural programming languages which gives us some clues as to what you imagine you want this code to do.
Basically it looks like a "group-adjacent" problem. In XSLT 2.0 you would do
<xsl:for-each-group select="/data/test/loof/super_incompleted"
group-adjacent="emp_no">
...
</xsl:for-each-group>
If you're stuck with XSLT 1.0 then the usual approach is to process the sequence of nodes with a recursive named template rather than a for-each instruction: you pass the list of nodes as a parameter to the template, together with the current employee id, and then in the template you process the first node in the list, and call yourself recursively to process the remainder of the list.
#ChristianMosz has expanded this suggestion into working code.
Thanks for your reply.
I have used the "preceding-sibling::" which solved my problem. Now the code is something like this.
<xsl:for-each select="/data/test/loof/super_incompleted">
<xsl:variable name="curr_temp_emp_no2" select="emp_no"/>
<xsl:choose>
<xsl:when test="$curr_temp_emp_no2 != preceding-sibling::super_incompleted[1]/emp_no">
<xsl:value-of select="emp_no"/><fo:inline> - </fo:inline><xsl:value-of select="variable_desc"/></xsl:when>
<xsl:otherwise>
<xsl:value-of select="variable_desc"/>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="curr_temp_emp_no" select="emp_no"/>
</xsl:for-each>
Earlier the table was like this.
1- ABC,1- DFE,1- GFH
2- DFG,2- FGH,2- SDS,2- RTY
Now table looks like this.
1- ABC,DFE,GFH
2- DFG,FGH,SDS

Detecting space or text between node and its following-sibling using XSLT

I have an XML document which contains the following example extract:
<p>
Some text <GlossaryTermRef href="123">term 1</GlossaryTermRef><GlossaryTermRef href="345">term 2</GlossaryTermRef>.
</p>
I am using XSLT to transform this to XHTML using the following template:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>
This works quite well, however I need to insert a space between the two GlossaryTermRef elements if they appear next to each other?
Is there a way to detect whether there is either space or text between the current node and the following-sibling? I can't always insert a space GlossaryTermRef item, as it may be followed by a punctuation mark.
I managed to solve this myself my modifying the template as follows:
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
<xsl:if test="following-sibling::node()[1][self::GlossaryTermRef]">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:template>
Can anyone suggest a better way, or see any problems with this solution?
Firstly, "node()|text()" is a longwinded equivalent of "node()". Perhaps you meant "*|node()" which would select the element and text children but not the comments or PIs.
Your solution is probably as good as any. Another would be to use grouping:
<xsl:for-each-group select="node()" group-adjacent="boolean(self::GlossaryTermRef)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:for-each select="current-group()">
<xsl:if test="position() gt 1"><xsl:text> </xsl:text></xsl:if>
<xsl:apply-templates select="."/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
Naah, that's not pretty at all.
My next attempt would be to use sibling recursion (where the parent does apply-templates on the first child, and each child does apply-templates on the immediately following sibling), but I don't think that's going to be an improvement either.
What about this one? what do you feel?
<xsl:template match="GlossaryTermRef">
<a href="#{#href}" class="glossary">
<xsl:apply-templates select="node()|text()"/>
</a>
</xsl:template>

xslt comma separate list of values

I am creating a summary view of Microsoft InfoPath form(s) using a custom XSLT Stylesheet.
I have a selection of the radio buttons on the form which are clicked "Yes", "No"
e.g.
What is the primary construction of the building?
Steel Yes
No
Timber Yes
NO
etc....
In the stylesheet I have
<tr>
<td>
The primary contruction of the building is
<xsl:choose>
<xsl:when test="/my:myFields/my:BrickPrimaryConstruction= 'true'">
Brick
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="/my:myFields/my:TimberPrimaryConstruction = 'true'">
Timber Framed
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="/my:myFields/my:ConcretePrimaryConstruction = 'true'">
Concrete Framed
</xsl:when>
</xsl:choose>
etc....
What I want to achieve in the final HTML output is something like:
The primary construction of the building is Brick, Concrete Framed, Prefabricated
I have not got much experience of XSLT, but what is the best way of achieving this?
Avoiding this:
, Concrete Framed, Prefabricated,
or
Brick, , Prefabricated
Normally in C# I would assign to a string and check if it is empty before appending a comma and then trim commas on the ends however I know that I cannot assign variables in xslt.
EDIT
I also mentioned that I wanted to be able to reuse the function in other situations such as
<xsl:value-of select="/my:myFields/my:Road"/>,
<xsl:value-of select="/my:myFields/my:District"/>,
<xsl:value-of select="/my:myFields/my:City"/>,
<xsl:value-of select="/my:myFields/my:County"/>,
<xsl:value-of select="/my:myFields/my:Postcode"/>
where these would be separated by comma or a new line character, but there is the possibility that the "District" for example might be blank resulting in "Road, , City" etc.
Try this:
<tr>
<td>
The primary contruction of the building is
<xsl:for-each select="/my:myFields/my:*[ends-with(local-name(), 'PrimaryConstruction') and (.='true')]">
<xsl:if test="position()!=1" xml:space="preserve">, </xsl:if>
<xsl:choose>
<xsl:when test="self::my:BrickPrimaryConstruction">Brick</xsl:when>
<xsl:when test="self::my:TimberPrimaryConstruction">Timber Framed</xsl:when>
<xsl:when test="self::my:ConcretePrimaryConstruction">Concrete Framed</xsl:when>
etc...
</xsl:choose>
</xsl:for-each>
Basically, the for-each loops over all the relevant fields, so that you can check their position and only emit the comma if you're not on the first item (XSLT has a 1-based index). Since the for-each already filters only the relevant entries, we then only have to check the type, not whether the value is true or not.
Note that you can extend the same principle to emit an "and" for the last item instead of a comma if you like to do so.
Maybe look at Implode.
Then, you could do
<xsl:variable name="theItems">
<xsl:if test="/my:myFields/my:BrickPrimaryConstruction= 'true'">
<foo>Brick</foo>
</xsl:if>
</xsl:variable>
<xsl:call-template name="implode">
<xsl:with-param name="items" select="exsl:node-set($theItems)" />
</xsl:call-template>
You have to convert $theItems, which is a xml fragment, to a node-set. This can only be archived using non-standart methods. XML.com shows some methods to archive it using various XML processors.

XSLT: opening but not closing tags

Is there a way to open a tag and not close it? For example:
<xsl:for-each select=".">
<span>
</xsl:for-each>
This is my code: http://pastebin.com/1Xh49YN0 . As you can see i need to open on a when tag and close it on another when tag (row 43 and 63).
This piece of code is not valid because XSLT is not well formed, but is there a way to do a similar thing? Thank you
Move the content between the two existing xsl:choose elements to a new template
In the xsl:when, open and close your span. Inside the span, call this new template.
Add an xsl:otherwise to the xsl:choose, in this, call the template, without adding a span.
As a general point, try to use xsl:apply-templates a bit more often, rather than xsl:for-each, it should make it easier to understand what is going on.
You can't - XSLT isn't about generating a text file or a sequence of characters, it's about transforming one document tree into another. That the tree eventually gets serialized into a textual format is incidental.
This is why, for example, you can't choose between and in the output file - they're both represent exactly the same document tree.
You can almost always achieve what is intended by refactoring into separate templates that call each other.
You can use disable-output-escaping, but it's generally considered a bit of a hack, and I understand it's deprecated in XSLT 2.
Untested, obviously, but if I understood your original code correctly, this should be pretty close.
<xsl:choose>
<xsl:when test="$pos">
<xsl:for-each select="$s">
<xsl:choose>
<!-- inizio contesto -->
<xsl:when test="$pos[.=position()+$context_number]">
<xsl:text>INIZIO CONTESTO</xsl:text>
</xsl:when>
<!-- fine contesto -->
<xsl:when test="$pos[.=position()-$context_number]">
<xsl:text>FINE CONTESTO</xsl:text>
</xsl:when>
<!-- parola -->
<xsl:when test="$pos[.=position()]">
<span class="word"><xsl:value-of select="."/></span>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- stampo tutta la riga -->
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
Hint: <xsl:when test="(. - $current_pos) eq 0"> is equivalent to <xsl:when test=".=$current_pos">. ;-)

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)