Question

I have an XSLT 1.0 match like this:

<xsl:template match="/ns:Library/ns:Book/@title | /ns:Library/ns:Book/@author | /ns:Library/ns:Book/@isbn | /ns:Library/ns:Book/@publisher"/>

Is there a shorter way to write this?

I would hope for something like:

<xsl:template match="/ns:Library/ns:Book/@*[title|author|isbn|publisher]"/>

But obviously that doesn't work.

Was it helpful?

Solution

The correct way to write what you have attempted is:

<xsl:template match="/ns:Library/ns:Book/@*[name() = 'title' or name() = 'author' or name() = 'isbn' or name() = 'publisher']"/>

which is of course not really considerably shorter. But there are other ways to write code that is more concise:

1 Write separate templates for the element and attribute matches

To avoid overly long expressions you could split your code into separate templates. In a first template, match the element:

<xsl:template match="/ns:Library/ns:Book">
   <xsl:copy>
     <xsl:apply-templates select="@*"/>
   </xsl:copy>
</xsl:template>

then, write a template for attributes:

<xsl:template match="@title|@author|@isbn|@publisher">
   <!--Process attributes-->
</xsl:template>

2 Specify which attributes should not be matched

<xsl:template match="/ns:Library/ns:Book/@*[name() != 'date']"/>

Ultimately, it depends on your input XML data and the XSLT context whether any of those methods makes sense. For example, if there are, say, 100 attributes that you don't want to match, then the second idea is not helpful.

Your main focus should be first on accuracy (producing the correct output XML), second on readability of code. If changes to your XSLT code do not improve either of those, then perhaps it's a bad idea to change the stylesheet at all.

OTHER TIPS

XSLT is very much context driven. Without seeing how (from which context) this template is being applied, (and without seeing the XML and the rest of the stylesheet), any answer is pretty much a guess. Technically, you could write:

<xsl:template match="@title|@author|@isbn|@publisher"/>

and apply this template from the context of /ns:Library/ns:Book.


Keep in mind that XSLT is naturally verbose and brevity is rarely a top priority.

In XSLT 3.0 you can write

match="/ns:Library/ns:Book/(@title | @author)"

In 1.0 a possible option is

match="/ns:Library/ns:Book/@*[name()='author' or name()='title']"

but I prefer the verbose original. In Saxon at least (I can't speak for other processors) a pattern that matches explicit names will always be much faster than one that matches a wildcard (* or @*) and relies on predicates.

Are you sure you really need the "/ns:Library/ns:Book/" part? Are you sure that match="@title|@author" wouldn't do the job?

For the sake of being shorter with XSLT 1.0 I'd often write something like:

<xsl:template match="/ns:Library/ns:Book/@*[contains('title|author|isbn|publisher',name())]"/>

Or using a global variable:

<xsl:variable name="attrNames">title|author|isbn|publisher</xsl:variable>

<xsl:template match="/ns:Library/ns:Book/@*[contains($attrNames,name())]"/>

Edit

As @nwellnhof mentioned this would also include attributes that match only parts of the string like it, auth, sb, pub etc, so this makes only sense if those cases are known to not be present.


In XSLT 2.0 my prefered solution would be:

<xsl:variable name="attrNames" select="'title','author','isbn','publisher'"/>

<xsl:template match="/ns:Library/ns:Book/@*[name() = $attrNames]"/>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top