wrong element in transformed xml

  • Thread starter Thread starter Andrus
  • Start date Start date
A

Andrus

xml data

<?xml version="1.0" ?>
<statement>
<accounts>
<account number="22">
<currency symbol="USD">
<transactions>
<transaction>
<id>1</id>
</transaction>
</transactions>
</currency>
<currency symbol="EUR">
<transactions>
<transaction>
<id>2</id>
</transaction>
</transactions>
</currency>
</account>
</accounts>
</statement>

transformed with msxml parser using stylesheet

<?xml version="1.0" encoding="ISO-8859-1" standalone="no" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" standalone="yes"/>
<xsl:template match="/">
<xsl:element name="VFPData">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="/statement/accounts/account/currency/transactions/*">
<xsl:element name="result">
<xsl:element name="id"><xsl:value-of select="id"/></xsl:element>
<xsl:element name="currency"><xsl:value-of
select="../../../currency/@symbol"/></xsl:element>
</xsl:element>
</xsl:template>
</xsl:stylesheet>


produces


<?xml version="1.0" encoding="UTF-16" standalone="yes"?>
<VFPData><result>
<id>1</id>
<currency>USD</currency>
</result>
<result><id>2</id>
<currency>USD</currency>
</result></VFPData>


In this output currency element value for id 2 is wrong: it must be EUR

How to change stylesheet so that EUR is in id 2 record ?

Andrus.
 
Off-topic, btw.

You are going back too many levels

At a guess (untested), try ="../../@symbol"

Note, however, that I prefer the "build upwards" approach - i.e. I'd
stop at "currency", and have something like:

<xsl:variable name="symbol" select="@symbol"/>

then I'd perform the downward matches, and I can use this in later
code, such as <xsl:value-of select="$symbol"/>. Note that xsl
variables are immutable within their declaring current scope.

Another thought: you're doing things the hard way. There is no need to
use <xsl:element/> unless the name is unknown. Consider (untested, for
demonstratio only):

<xsl:template match="/">
<VFPData>
<xsl:apply-templates select="/statement/accounts/account/
currency">
</VFPData>
</xsl:template>

<xsl:template match="/statement/accounts/account/currency">
<xsl:variable name="symbol" select="@symbol"/>
<xsl:foreach select="transactions/transaction">
<result>
<id><xsl:value-of select="id"/></id>
<currency><xsl:value-of select="$symbol"/></currency>
</result>
</xsl:foreach>
</xsl:template>


Note that attributes are even easier; you don't need to use
xsl:attribute much - instead of:
<el><xsl:attribute name="foo"><xsl:value-of select="bar"/></
xsl:attribute></el>
you just need
<el foo="{bar}"/>

Marc
 
Off-topic, btw.

You are going back too many levels

At a guess (untested), try ="../../@symbol"

I would suggest simply "ancestor::currency/@symbol".
Note, however, that I prefer the "build upwards" approach - i.e. I'd
stop at "currency", and have something like:

<xsl:variable name="symbol" select="@symbol"/>

then I'd perform the downward matches, and I can use this in later
code, such as <xsl:value-of select="$symbol"/>. Note that xsl
variables are immutable within their declaring current scope.

xsl:for-each is traditionally frowned upon in XSLT code when a
template would do. Just how pragmatical it is, is, of course, a matter
of debate - but personally, I've dealt with a lot of stylesheets of
both kinds, and those that were heavy on templates were invariably
more maintainable than those who did the same with for-each. Perhaps
it's just my luck.
 
I would suggest simply "ancestor::currency/@symbol".

That traverses the entire ancestor axis and isn't very specific; while
I don't especially like upwards ".." navigation, it at least makes it
clear how many levels we are expecting to look, which can be important
if the xml might have nested elements with the same name (since IIRC
the axis still returns nodes in document order, not axis order).
xsl:for-each is traditionally frowned upon in XSLT code when a
template would do.

Actually, in a lot of discussions I've had the conclusion has always
been "whatever keeps the code simplest" - and in this case the
enumeration of the sub elements is an integral part of the current
operation. You could use a template, but using the variable approach
[which I was demonstrating] that would involve declaring and passing a
template argument, which seems a little overkill - and it certainly
makes the example more complex ;-p. But yes, it could be done either
way. If we were handling different types of sub-element then yes, I'd
use a template.

Marc
 
That traverses the entire ancestor axis and isn't very specific; while
I don't especially like upwards ".." navigation, it at least makes it
clear how many levels we are expecting to look, which can be important
if the xml might have nested elements with the same name (since IIRC
the axis still returns nodes in document order, not axis order).

The _results_ are returned in document order, but the _traversal_ is
in the axis order (as it is for any reverse axis - defined by XPath as
"an axis that only ever contains the context node or nodes that are
before the context node in document order"), so it will find the
closest ancestor with the given element name.
 
The _results_ are returned in document order, but the _traversal_ is
in the axis order (as it is for any reverse axis - defined by XPath as
"an axis that only ever contains the context node or nodes that are
before the context node in document order"), so it will find the
closest ancestor with the given element name.

A slight correction: my originally proposed XPath expression would
still have returned all ancestor elements with a given name, if there
were more than one. However, this:

ancestor::currency[1]/@symbol

would indeed return the closest ancestor.
 
Back
Top