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 createelements
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 akey
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 thiskey
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?
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 n
th 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 thexsl:key
element;the value of the
name
attribute of thexsl:key
element is equal to y; andwhen the expression specified in the
use
attribute of thexsl: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 thestring
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>
精彩评论