Match an attribute value in a set of possible values

P

patrin

Hi All,

given the source document:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>
<test id="1" name="first child"/>
</child>
<child>
<test id="2" name="second child"/>
</child>
<child>
<test id="3" name="third child"/>
</child>
</root>

I would suppress the the child elements that contain a test element
with the value of the id attribute in a list of known ids; for
instance, assuming that the list of ids to remove is {1,3}, I would
obtain the document:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>
<test id="2" name="second child"/>
</child>
</root>

where only the second child element is retained.

I'm only able to suppress a single child element using the following
XSL transform;

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
<xsl:eek:utput method="xml" indent="yes" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child[test[@id='1']]"/>
</xsl:stylesheet>

Is it possible to match and suppress multiple child elements whose ids
are contained in a given list ?

Since I'm invoking the transform from Java, I would like to set a
template parameter to the list of ids to be removed; e.g. as comma
delimited list, and write the last match condition as something like
this:

....
<xsl:param name="idToRemove" select="default" />
<xsl:template match="child[test[@id in $idsToRemove]]"/>
....

Note that I'm using XSLT 1.0, so I cannot leverage the enhancements of
XSLT 2.0, but I'm wondering if there is some trick to do the job; for
instance injecting the list of ids to remove into the same XSLT
document and then iterating over them and suppressing any matching
child element from the source document... maybe something like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:data="http://sample.array.parameters/data" version="1.0">
<xsl:eek:utput method="xml" indent="yes" />
<data:parameters>
<id>1</id>
<id>3</id>
</data:parameters>
<xsl:variable name="parameters-root"
select="document('')/*/data:parameters"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child[test[@id in $parameters-root]]"/>
</xsl:stylesheet>

Thanks a lot for any suggestion

Regards,

Patrizio
 
M

Martin Honnen

patrin said:
<xsl:template match="child[test[@id='1']]"/>

Well an or is possible in XPath 1.0

<xsl:template match="child[test[@id='1' or @id = '3']]"/>
 
P

patrin

Martin said:
patrin said:
<xsl:template match="child[test[@id='1']]"/>

Well an or is possible in XPath 1.0

<xsl:template match="child[test[@id='1' or @id = '3']]"/>

Thanks for the quick replay, but the list of ids to match is dynamyc
and I know it only from Java code, where it is stored into a String
arrray:

String[] idsToRemove = {1,3,...};

and I would like to pass it to the Transformer, invoking the method:

setParameter("idsToRemove", idsToRemove);

so that the XSLT should remove all the child elements whose test
subelement has an id with any of the values contained in the
idsToRemove array.

Is it possible to do this with XSLT 1.0 ? or can you suggest an
alternative way to solve the problem ?

Thanks again for your help

Regards,

Patrizio
 
J

Joseph Kesselman

patrin said:
setParameter("idsToRemove", idsToRemove);

Well, you're unlikely to be able to pass in an array of strings and do
anything useful with it except (maybe) pass it back out to an extension
function. Xalan, for example, will treat that as a "non-XSLT type".

I'd suggest reformatting the data as a single string, which most
processors should treat as an XSLT string value, and then doing string
manipulation to check if the ID is contained in that list. I did a
quick-and-dirty version of that in my styling-stylesheets article; since
it was just an illustration I didn't make it highly robust but you could
make it stronger by checking delimiters.

Alternatively, some processors (again, Xalan is one of these) may let
you pass in a DOM node and have XSLT treat it as a node-set. If you
construct a tree where one element has each of the values, you could
then do an XSLT node-set comparison (does any node in one set equal any
node in the other set). The stylesheet would be cleaner, but the
preparation would be more complex. I'm not sure how portable this
approach would be; the TrAX APIs don't make any particular promises
about how non-string parameters will be processed.

Alternatively: Build that nodes-I'm-interested-in XML document as text,
and put it somewhere that the stylesheet can access via the document()
function. Less efficient, but it gives you the simple "does anything
equal" syntax in the stylesheet, and it may be more portable than
relying on the API to do something useful with Nodes.
 
M

Martin Honnen

patrin wrote:

Thanks for the quick replay, but the list of ids to match is dynamyc
and I know it only from Java code, where it is stored into a String
arrray:

String[] idsToRemove = {1,3,...};

and I would like to pass it to the Transformer, invoking the method:

setParameter("idsToRemove", idsToRemove);

so that the XSLT should remove all the child elements whose test
subelement has an id with any of the values contained in the
idsToRemove array.

Is it possible to do this with XSLT 1.0 ?

I guess there are two approaches, one that might be processor dependent
where you need to look at the documentation of the XSLT processor what
kind of types you can pass in with setParameter:

The setParameter method
<http://java.sun.com/j2se/1.5.0/docs...Parameter(java.lang.String, java.lang.Object)>
as the second argument takes any object so it might well be that it
works to pass in a Source (e.g. DOMSource or SAXSource or StreamSource)
there as well. But details depend on the XSLT processor, I don't think I
have tried that so far with Saxon or Xalan.


The second approach would take the XSLT stylesheet as an XML document
and change it as needed depending on your idsToRemove before the
stylesheet is used for the transformation.
 
D

Dimitre Novatchev

As others have commented, there are different way to structure the
externally passed parameter or even to modify dynamically the stylesheet, so
that passing a parameter will not be necessary.

But your initial question was how to find that the @id attribute of the
current element is part of ids list, encoded as a coma-delimited string. One
way to determine this is using the following XPath 1.0 expression:

contains($idList, concat(' ', @id, ','))

This will work if every id in the list starts with at least one space and is
followed immediately by a coma. The global parameter itself will have to be
produced and passed to the stylesheet by an implementation-dependent
component -- as this varies for different XSLT processors, one needs to
study the documentation of the specific XSLT processor used in order to
understand how this should be done.

Certainly there are ways to perform more precise tokenization (one example
is the tokenization implemented in the FXSL library), but this simple
solution is probably good just for a start.

Cheers,
Dimitre Novatchev

patrin said:
Hi All,

given the source document:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>
<test id="1" name="first child"/>
</child>
<child>
<test id="2" name="second child"/>
</child>
<child>
<test id="3" name="third child"/>
</child>
</root>

I would suppress the the child elements that contain a test element
with the value of the id attribute in a list of known ids; for
instance, assuming that the list of ids to remove is {1,3}, I would
obtain the document:

<?xml version="1.0" encoding="UTF-8"?>
<root>
<child>
<test id="2" name="second child"/>
</child>
</root>

where only the second child element is retained.

I'm only able to suppress a single child element using the following
XSL transform;

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
<xsl:eek:utput method="xml" indent="yes" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child[test[@id='1']]"/>
</xsl:stylesheet>

Is it possible to match and suppress multiple child elements whose ids
are contained in a given list ?

Since I'm invoking the transform from Java, I would like to set a
template parameter to the list of ids to be removed; e.g. as comma
delimited list, and write the last match condition as something like
this:

...
<xsl:param name="idToRemove" select="default" />
<xsl:template match="child[test[@id in $idsToRemove]]"/>
...

Note that I'm using XSLT 1.0, so I cannot leverage the enhancements of
XSLT 2.0, but I'm wondering if there is some trick to do the job; for
instance injecting the list of ids to remove into the same XSLT
document and then iterating over them and suppressing any matching
child element from the source document... maybe something like this:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:data="http://sample.array.parameters/data" version="1.0">
<xsl:eek:utput method="xml" indent="yes" />
<data:parameters>
<id>1</id>
<id>3</id>
</data:parameters>
<xsl:variable name="parameters-root"
select="document('')/*/data:parameters"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="child[test[@id in $parameters-root]]"/>
</xsl:stylesheet>

Thanks a lot for any suggestion

Regards,

Patrizio
 

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

Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top