user3368148
user3368148

Reputation: 31

XSLT how to add an attribute into an existing template without changing it

I am trying to customise an existing XSLT template by adding a default attribute value if it is missing. The original template is from 3rd party and better not to be touched. Below is the code to illustrate what I want to achieve. Any help will be greatly appreciated.

The sample xml file to be transformed:

<?xml version="1.0" encoding="UTF-8"?>
<images>
   <image href="https://cityyearnh.files.wordpress.com/2011/02/boat.jpg" width="300" />
   <image href="https://cityyearnh.files.wordpress.com/2011/02/boat.jpg"/>
</images>

Please note, there are 2 images in the xml file, one with @width attribute and one without. When @width is not defined, the XLST file below will not add a width attribute in the output html file.

The existing XSLT file

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
    <html><body>
       <h2>My Images</h2>
       <xsl:apply-templates select="images/image" />
    </body></html>
</xsl:template>

<xsl:template name="img_template" match="images/image">
    <xsl:text>&#10;</xsl:text>
    <img>
    <xsl:apply-templates select="@href|@width" />
    </img>
    <br />
</xsl:template>

<xsl:template match="/images/image/@href">
    <xsl:attribute name="src"><xsl:value-of select="." /></xsl:attribute>
</xsl:template>

<xsl:template match="/images/image/@width">
    <xsl:attribute name="width"><xsl:value-of select="." /></xsl:attribute>
</xsl:template>

When @width is not defined, I want to add a width attribute of 50% in the output html file. I could modify the original img_template to achieve what I want, for example,

<xsl:template name="img_template" match="images/image">
    <xsl:text>&#10;</xsl:text>
    <img>
       <xsl:apply-templates select="@href" />
       <xsl:if test="@width">
          <xsl:apply-templates select="@width" />
       </xsl:if>
       <xsl:if test="not(@width)">
          <xsl:attribute name="width">50%</xsl:attribute>
       </xsl:if>
    </img>
    <br />
</xsl:template>

However, it's not a good idea to modify the original template. Is there a way to call the original template and add some additional processing ?

I have tried to add an overriding template that calls the original and somehow add the default width attribute (but I do not know how). My code so far:

 <xsl:template match="images/image">
    <xsl:if test="@width">
       <xsl:call-template name="img_template" />
    </xsl:if>
    <xsl:if test="not(@width)">
       <p>Below image has no width attribute set. Add 50% width somehow ?</p>
       <xsl:call-template name="img_template" />
    </xsl:if>
 </xsl:template>

Any help/suggestions will be greatly appreciated.

Upvotes: 1

Views: 1527

Answers (3)

user3368148
user3368148

Reputation: 31

Following @MichaelKey's suggestion, I have worked out the following solution:

XSLT 2.0

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="original.xsl" />

<xsl:template match="images/image[not(@width)]">
    <xsl:variable name="old_content">
        <xsl:call-template name="img_template" />
    </xsl:variable>
    <xsl:call-template name="re-generate">
        <xsl:with-param name="oldcontent" select="$old_content"/>
    </xsl:call-template>
</xsl:template>
<xsl:template name="re-generate">
    <xsl:param name="oldcontent" />
    <img width="50%">
       <xsl:copy-of select="$oldcontent/img/@*" />
    </img>
    <xsl:copy-of select="$oldcontent/*[not(self::img)]" />
</xsl:template>
</xsl:stylesheet>

In the above, I have assumed that the original template is in file original.xsl. Also, the first node generated by img_template is assumed to be the img. For more complex processing, it would be ideal to pass to a java function.

Upvotes: 0

Michael Kay
Michael Kay

Reputation: 163635

I've been dealing with a similar case recently where I was invoking a library stylesheet that put its output in a namespace, and I wanted it in no namespace. It would have meant overriding a lot of code. So instead I captured the output of the existing stylesheet in a variable, and transformed it.

Something like:

<xsl:template match="image">
  <xsl:variable name="temp">
    <xsl:apply-imports/>
  </xsl:variable>
  .. now process $temp e.g. to add missing attributes
</xsl:template>

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 117165

it's not a good idea to modify the original template.

Why not? I would rewrite your stylesheet as:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/images">
    <html>
        <body>
            <h2>My Images</h2>
            <xsl:apply-templates select="image" />
        </body>
    </html>
</xsl:template>

<xsl:template match="image">
    <img src="{@href}" width="50%">
        <xsl:apply-templates select="@width" />
    </img>
    <br/>
</xsl:template>

<xsl:template match="@width">
    <xsl:attribute name="width">
        <xsl:value-of select="." />
    </xsl:attribute>
</xsl:template>

</xsl:stylesheet>

This assigns a width attribute with a default value of 50% to every image. If the image does have its own width attribute, it will be used t overwrite the default value.


Alternatively, you could do:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/images">
    <html>
        <body>
            <h2>My Images</h2>
            <xsl:apply-templates select="image" />
        </body>
    </html>
</xsl:template>

<xsl:template match="image">
    <img src="{@href}">
        <xsl:attribute name="width">
            <xsl:choose>
                <xsl:when test="@width">
                    <xsl:value-of select="@width" />
                </xsl:when>
                <xsl:otherwise>50%</xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
    </img>
    <br/>
</xsl:template>

</xsl:stylesheet>

which is perhaps more verbose, but very clear.


Added:

Unfortunately, the original XSLT file is complex and comes as part of a software package. We do not suppose to modify any files there for maintenance reasons. Are there any ways to achieve what I want by using "overrides" ?

Yes, you could add the following template:

<xsl:template match="image[not(@width)]" priority="1">
    <img src="{@href}" width="50%"/>
    <br/>
</xsl:template>

Upvotes: 1

Related Questions