0

Using Apache FOP, I want to collect some info in a PDF file. The XML source has some child nodes a to e, let's say

<node>
   <a>some val</a>
   <b>some other val</b>
   <c>more val</c>
   <d>even more val</d>
   <e>a last val</e>
</node>

I don't want to display all of them. a,b,c shall always be displayed but may be emtpy. The maximum amount of displayed values is 3. So, d and e are optional and must be kept in that order.

Sadly, the XML structure cannot be modified.

What is the right XSLT for that? I tried

<xsl:for-each select="child::*[name()='a' or name() = 'b' or name() = 'c' or name() = 'd' or name() = 'e'][string-length(.)&gt;0]">
    <xsl:if test="position() &lt;= 3">
        <xsl:value-of select="name()"/>
    </xsl:if>
</xsl:for-each>

but that doesn't bring me an ordered list. :(

Florian Ruh
  • 110
  • 8

3 Answers3

1

<xsl:sort /> should hep.

In your case it would be:

<xsl:sort select="name()"/>

Therefore try:

    <xsl:for-each select="child::*[name()='a' or name() = 'b' or name() = 'c' or name() = 'd' or name() = 'e'][string-length(.)&gt;0]">
        <xsl:sort select="name()"/>
        <xsl:if test="position() &lt;= 3">
            <xsl:value-of select="name()"/>
        </xsl:if>
    </xsl:for-each>

Update: Because in real live input XML there is not useful information to sort by you may add some meta information. Where to store the meta information depend on the capabilities of the xslt processor.

If you can use the node-set() extension you may try something like this:

Add a variable to stylesheet with expected order.

xsl:variable name="myOrder">
        <order name="a" pos="1" />
        <order name="b" pos="3" />
        <order name="c" pos="2" />
        <order name="d" pos="4" />
        <order name="e" pos="5" />
    </xsl:variable>

Make this variable usable as node-set by:

<xsl:variable name="Order" select="exsl:node-set($myOrder)" />

Sort with help of this variable.

<xsl:sort select="$Order/order[@name= name(current())]/@pos"/>
hr_117
  • 9,589
  • 1
  • 18
  • 23
  • Thank you, that would help if I haven't simplified the example code. *g*. Actually, the node names are: a=Lagerort, b=Abteilung, c=Kostenstelle, d=Mitarbeiter, e=Lieferant. So there's no natural ordering I can follow here. – Florian Ruh Jul 15 '13 at 15:13
  • So the right answer for the wrong question? ;-) XSLT-1.0 or 2.0 etc.? And the nodes are not in the right order in input XML? – hr_117 Jul 15 '13 at 15:19
  • I'm not familiar wiht FOP. Can you use an extension like node-set()? Or If not can you add some metha data to your xml file which holds a mapping from name to position? – hr_117 Jul 15 '13 at 15:42
  • FOP = XSLT 1.0 only. :( And even worse: order of nodes is not the order I want to display them. – Florian Ruh Jul 15 '13 at 15:43
  • thank you very much. That update with the customized ordering is exactly what I needed! I only had to add ` xmlns:exsl="http://exslt.org/common"` and woooosh! :) – Florian Ruh Jul 16 '13 at 08:36
1

@Florian Ruh, your stated requirement is not self-consistent: "I don't want to display all of them. a,b,c shall always be displayed. The maximum amount of displayed values is 3. So, d and e are optional and must be kept in that order."

If a, b and c are always displayed, and the maximum number of displayed values is 3, then there is no chance for d and 3 to be displayed.

Please clarify your requirement.

Note that it is very poor form to use the name() function as you have.

The equivalent to:

<xsl:for-each select="child::*[name()='a' or name() = 'b' or name() = 'c' or name() = 'd' or name() = 'e'][string-length(.)&gt;0]">

is:

<xsl:for-each select="(a|b|c|d|e)[string(.)]">

... and the approach I posit is namespace-safe, while the approach you used is not.

G. Ken Holman
  • 4,333
  • 16
  • 14
0

Perhaps I am not understanding correctly, but if you want to only return the first three items into an ordered list, you could do something like

<xsl:template match="node">
<fo:list-block>
<xsl:apply-templates/>
</fo:list-block>
</xsl:template>

And

<xsl:template match="node/*[4] | node/*[5]"/>
<xsl:template match="node/*[1] | node/*[2] | node/*[3]">
<fo:list-item>
<fo:list-item-label>
<fo:block><xsl:value-of select="position()"/></fo:block>
</fo:list-item-label>
<fo:list-item-body>
<fo:block><xsl:value-of select="."/></fo:block>
</fo:list-item-body>
</fo:list-item>
</xsl:template>

This will ignore any 4th and 5th elements, but process the first, second, and third in document order (since that's the order in which they are encountered).

  • Thanks for that answer. Good idea to ignore 4th and 5th element that way. However, the list I have isn't ordered and the elements may be empty. – Florian Ruh Jul 16 '13 at 07:33