wrong element in transformed xml

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:blush:utput 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.
 
M

Marc Gravell

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
 
P

Pavel Minaev

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.
 
M

Marc Gravell

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
 
P

Pavel Minaev

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.
 
P

Pavel Minaev

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top