Processing just one node when multiple nodes have the same predicate selector

341 views
Skip to first unread message

Alan Meyer

unread,
May 3, 2012, 6:08:38 PM5/3/12
to XSLT
Ladies and Gentlemen:

Consider the following document:

<a>
<b foo='0'>...</b>
<b foo='9'>...</b>
<b foo='0'>
<c foo='2'>...</c>
<c foo='0'>...</c>
</b>
<a>

The "foo='0'" attribute in any element in the document is a
signal that certain tasks need to be performed. The task does
some work in a database. When it's done it modifies the document
to change the value of foo to a new, unique number.

There are three elements with foo="0" in this particular example
document, two b elements and one c element. When the work is all
done, the document should look something like this:

<a>
<b foo='21'>...</b>
<b foo='9'>...</b>
<b foo='22'>
<c foo='2'>...</c>
<c foo='23'>...</c>
</b>
<a>

I have written an XSLT filter that can locate all of the elements
with foo="0" attributes and can replace a "0" with a new value
passed as a parameter to the filter. The way I'm finding these
elements is with the following pattern:

<xsl:template match = '*[@foo="0"]'>
...
</xsl:template>

So far, so good. But here's the problem:

I need to replace every "0" with a new value. I don't know in
advance how many foo="0" attributes will be encountered. There
might be just one, but there might be more than one.

The filter I've written will find all of the foo's with the value
"0", and replace ALL of them with the same passed value. That
isn't what I need to do. I need to replace each one with a
different passed value. The values are actually significant and
I can't just use a counter to insert them.

So what I want to do is to invoke the filter multiple times, once
for each foo="0" that is encountered. But to do that, I need to
just replace one foo and return a converted document, then filter
again, and so on.

The efficiency isn't an issue here. This happens rarely. I'm
more concerned about getting it right and producing a readable,
maintainable filter script.

Does anyone have a way to do it?

Here is the complete script that I'm using, in case someone needs
to see it or experiment with it. Please feel free to laugh or
insult me if you think my approach warrants that. However, if
you do, I will be grateful if you can provide a better solution.

Thanks.

Alan

------------------------ cut here ------------------------------
<xsl:transform version = '1.0'
xmlns:xsl = 'http://www.w3.org/1999/XSL/Transform'
xmlns:cdr = 'cips.nci.nih.gov/cdr'>
<xsl:output method = 'xml'/>

<xsl:param name = "addTargValue"/>

<!-- Copy almost everything straight through -->
<xsl:template match = '@*|comment()|*|processing-instruction()|
text()'>
<xsl:copy>
<xsl:apply-templates
select = '@*|comment()|*|processing-instruction()|text()'/>
</xsl:copy>
</xsl:template>

<!-- Replace elements with our requested attribute -->
<!-- XXX HOW DO I DO THIS ONLY ONCE? XXX -->
<xsl:template match = '*[@foo="0"]'>
<xsl:element name = '{name()}'>
<xsl:for-each select = '@*'>
<xsl:choose>
<xsl:when test = 'name()="foo"'>
<xsl:attribute name = 'foo'>
<xsl:value-of select = '$addTargValue'/>
</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:transform>

dshcs

unread,
May 4, 2012, 4:37:24 PM5/4/12
to XSLT
Assuming the process that is calling the transform knows all the new
foo values at one time, they can be passed into the XSLT several
ways. Multiple parameters (assuming a maximum), a parseable string
with the new values in the order they would be found in the input
document or document fragment that might need a little less XSLT
code. The following example imbeds the document fragment in the XSLT,
but you should be able to pass it as a parameter or imbed it in the
input document as a separate node and remove it as part of the output
process. The example only shows how to find the replacement foo value
in the document fragment, the transform would have to use that
technique to replace the zero foo value as the transform progressed.

XML:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="XmlTest2.xsl"?>
<a>
<b foo='0'>...</b>
<b foo='9'>...</b>
<b foo='0'>
<c foo='2'>...</c>
<c foo='0'>...</c>
</b>
</a>

XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">

<xsl:variable name="update">
<update>
<x foo="221"/>
<x foo="222"/>
<x foo="223"/>
</update>
</xsl:variable>

<xsl:for-each select="//*[@foo='0']">
newfoo=<xsl:value-of select="msxsl:node-set($update)//*[@foo]
[count(current()/preceding::*[@foo='0'] | current()/
ancestor::*[@foo='0'])+1]/@foo"/>
<br/>
</xsl:for-each>

</xsl:template>
</xsl:stylesheet>

Output:
newfoo=221
newfoo=222
newfoo=223

Alan Meyer

unread,
May 4, 2012, 6:47:20 PM5/4/12
to XSLT


On May 4, 4:37 pm, dshcs <dsh...@embarqmail.com> wrote:
> Assuming the process that is calling the transform knows all the new
> foo values at one time, they can be passed into the XSLT several
> ways.

That is clever and creative!

I think I'm going to try that.

Thanks.
Reply all
Reply to author
Forward
0 new messages