开发者

xslt <key use="position()" ../> problem

I've got a (very excel spreadsheet like..) xml data, from which I'm supposed to create a more readable one.

I have the headers on the top of the structure, 开发者_开发问答and would like to create elements from their text value and apply them on the rest of the document.

Probably the actual data speaks clearer, so my input document looks like

<?xml version="1.0"?>
<root>
    <headers>
        <header>line</header>
        <header>product</header>
        <header>order</header>
        <header>qty</header>
        <header>deadline</header>
    </headers>
    <row>
        <data>2</data>
        <data>HU12_SETUP</data>
        <data>16069061</data>
        <data>1</data>
        <data>2011-04-13T09:22:59.980</data>
    </row>
    <row>
        <data>1</data>
        <data>40PFL7605H/12</data>
        <data>16310360</data>
        <data>200</data>
        <data>2011-04-13T09:22:59.980</data>
    </row>
</root>

and my goal is to have an xml document as:

<?xml version="1.0"?>
<morning>
    <row>
        <line>2</line>
        <product>HU12_SETUP</product>
        <order>16069061</order>
        <qty>1</qty>
        <deadline>0</deadline>
    </row>
    <row>
        <line>1</line>
        <product>40PFL7605H/12</product>
        <order>16310360</order>
        <qty>200</qty>
        <deadline>77</deadline>
    </row>
</morning>

I'd like to do this in a "proper" / "efficient" way, that's why I'm turning to you, guys, to help me out.

I figured using a key to match data positions to header positions would be my solution, but for some reason it just doesn't work (I'am already ~X( ).

What I need is to point out what is wrong with my xsl, and/or if there's something wrong with the key concept, advise me better solution.

Here is my (debugging) xsl:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    <xsl:key name="header" match="header" use="position()" />

    <xsl:template match="/">
        <morning>
            <xsl:apply-templates />
        </morning>
    </xsl:template>
    <xsl:template match="headers" />
    <xsl:template match="row">
        <xsl:copy>
            <xsl:apply-templates />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="data">
        <xsl:element name="{concat('bla-',position())}">
            <xsl:value-of select="key('header',position())" />
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

where I verify that the position() is actually correct.

My output varies depending on which stylesheet version I use.

output 1.0:

<?xml version='1.0' encoding='UTF-8' ?>
<morning>
  <row>
    <bla-1>line</bla-1>
    <bla-2/>
    <bla-3/>
    <bla-4/>
    <bla-5/>
  </row>
  <row>
    <bla-1>line</bla-1>
    <bla-2/>
    <bla-3/>
    <bla-4/>
    <bla-5/>
  </row>
</morning>

output 2.0:

<?xml version='1.0' encoding='UTF-8' ?>
<morning>
  <row>
    <bla-1>line product order qty deadline</bla-1>
    <bla-2/>
    <bla-3/>
    <bla-4/>
    <bla-5/>
  </row>
  <row>
    <bla-1>line product order qty deadline</bla-1>
    <bla-2/>
    <bla-3/>
    <bla-4/>
    <bla-5/>
  </row>
</morning>

As you can see, key('header',position()) gives me empty string in all the cases except the first (that's why i have it as value, not element name).

I would appreciate any help, thank you in advance!

Addition:

Based on @LarsH's and @Alejandro's answer (still stuck to this key thing...) I came up with:

<xsl:template match="data">
    <xsl:variable name="posn" select="position()" />
    <xsl:element name="{key('header',1)[$posn]}" />
    <xsl:element name="{key('header',1)[position()]}" />
</xsl:template>

I can see that using a key here with a static 1 is dumb, but I'm getting scared why the two resulting elements from up there do not match?

The first is correct, the second always gives me line, respectively lineproductorderqtydeadline back.

Could anyone point me to the right direction?


The difference in output between XSLT 1.0 and 2.0 is that in 1.0, xsl:value-of outputs the string value of the first node in the selected nodeset, whereas (IIRC) in 2.0, it outputs the concatenated values of all the nodes in the selected nodeset, with space as the default delimiter. (Check on that before you believe me though.)

So based on your output, it looks like the key value for each header element is always 1. I'm not sure what position() would yield when used in the use attribute of a key (it depends on the context in a way that I haven't looked up), so that's plausible to me.

Rather than using a key with position(), I would try something like:

 <xsl:key name="header" match="header"
          use="count(preceding-sibling::header) + 1" />

This works. It might be slow if you had thousands of headers, but I assume you never would.

Alternatively, you could decide not to use keys, and instead do

<xsl:template match="data">
    <xsl:variable name="posn" value="position()" />
    <xsl:element name="{concat('bla-', $posn)}">
        <xsl:value-of select="/root/headers/header[$posn]" />
    </xsl:element>
</xsl:template>

Usually keys are used for better performance, but in this case it seems unclear that there would be any significant performance advantage vs. using the [$posn] predicate.

Addition:

The answer to your "addition" above is long enough it needs to be in here rather than a comment. In your addition:

<xsl:template match="data">
    <xsl:variable name="posn" select="position()" />
    <xsl:element name="{key('header',1)[$posn]}" />
    <xsl:element name="{key('header',1)[position()]}" />

As stated in my comment, the context includes both the current node and the current node list. For the first position(), in the select of the variable, the current node list consists of all the data elements that are children of the row element being processed. So position() yields the position of the current node (a data element) in that list. This value is stored in the $posn variable.

key('header',1) yields a node list of all header elements, for reasons explained above: the key value for each header is always 1. So key('header',1)[$posn] gives the nth header element, where n is the position of the current data element among its siblings.

For the second position() call: In a predicate, the context is determined by applying the predicate individually to each node in the node list produced by the XPath expression up to that point, with that node list as the current node list. So inside the square brackets of key('header',1)[...], the context node list is the node list returned by key('header',1). Again, this is the list of all header elements. So for each header element, position() here returns the position of that header among its siblings.

Now we get a little deeper... Predicates are boolean by nature, but when the expression e in a predicate is numeric, it is treated as an abbreviation for position() = e. So your second element name expression is equivalent to

    <xsl:element name="{key('header',1)[position() = position()]}" />

and position() = position() is always true for any given context, so the above is equivalent to

    <xsl:element name="{key('header',1)}" />

which is the list of all headers.


From http://www.w3.org/TR/xslt#key

Thus, a node x has a key with name y and value z if and only if there is an xsl:key element such that:

  • x matches the pattern specified in the match attribute of the xsl:key element;

  • the value of the name attribute of the xsl:key element is equal to y; and

  • when the expression specified in the use attribute of the xsl:key element is evaluated with x as the current node and with a node list containing just x as the current node list resulting in an object u, then either z is equal to the result of converting u to a string as if by a call to the string function, or u is a node-set and z is equal to the string-value of one or more of the nodes in u.

It means that position() in use attribute is always 1.

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:strip-space elements="*"/>
    <xsl:variable name="vHeaders" select="/root/headers/header"/>
    <xsl:template match="/">
        <morning>
            <xsl:apply-templates />
        </morning>
    </xsl:template>
    <xsl:template match="headers" />
    <xsl:template match="row">
        <xsl:copy>
            <xsl:apply-templates />
        </xsl:copy>
    </xsl:template>
    <xsl:template match="data">
        <xsl:variable name="vPosition" select="position()"/>
        <xsl:element name="{$vHeaders[$vPosition]}">
            <xsl:apply-templates />
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

Output:

<morning>
    <row>
        <line>2</line>
        <product>HU12_SETUP</product>
        <order>16069061</order>
        <qty>1</qty>
        <deadline>2011-04-13T09:22:59.980</deadline>
    </row>
    <row>
        <line>1</line>
        <product>40PFL7605H/12</product>
        <order>16310360</order>
        <qty>200</qty>
        <deadline>2011-04-13T09:22:59.980</deadline>
    </row>
</morning>
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜