Re: [xsl] trouble re-rendering XHTML using xsl

Subject: Re: [xsl] trouble re-rendering XHTML using xsl
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Fri, 15 Feb 2002 08:18:23 +0000
Hi Bruce,

> What I am hoping to achieve is any form element in <HTML ...> tree
> that has a corresponding entry in the <RenderingControl> tree will
> convert its attributes to the new one (re-rendering essentially),
> and any part of the html that doesn't correspond to a
> RenderingControl should just be copied 'as is'.

OK, to make this kind of alteration, I recommend that you start with
an identity template so that the default action when you apply
templates to a node is that it gets copied as-is:

<xsl:template match="@*|node()">
  <xsl:copy>
    <xsl:apply-templates select="@*|node()" />
  </xsl:copy>
</xsl:template>

The only part of the source XML that you want this process to be
applied to is the HTML part - the html element underneath the NewPage
document element, and its descendants. So in the template that matches
the root node, you should specifically say that you want to apply
templates to the HTML part of the page:

<xsl:template match="/">
  <xsl:apply-templates select="NewPage/html" />
</xsl:template>

[Your example hinted that there was an xmlns attribute on the html
element - if there is, remember that this will change the namespace of
the html element and all its descendants, so you'll need to declare
that namespace in your stylesheet, with a prefix, and use that prefix
whenever you refer to those elements.]

If you run this stylesheet, you'll just get a plain copy of the HTML
part of the source XML. You want that HTML to be tweaked slightly.
Specifically, if there's an input element in the HTML, and its id is
the same as the Name of an ElementToBeChanged, then you want to use
the value of the ElementToBeChanged's RenderAs child to add a new
attribute to the input element.

So you need a special template for input elements. Basically, it
should create a copy of the input element as it is, with all its
attributes, so the basic form is:

<xsl:template match="input">
  <xsl:copy>
    <xsl:copy-of select="@*" />
  </xsl:copy>
</xsl:template>

But the other thing it needs to do is look at the RenderingControl
part of the page and work out whether there's an ElementToBeChanged
that uses its id. Personally, I'd manage this by setting up a key that
matches ElementToBeChanged elements based on their Name, as follows:

<xsl:key name="changes" match="ElementToBeChanged" use="Name" />

That enables you to do things like:

  key('changes', 'btnDoSomething')

and get back all the ElementToBeChanged elements that apply to the
input element whose id is btnDoSomething. Or more generally, you can
use:

  key('changes', @id)

to get the ElementToBeChanged elements that apply to the current input
element.

I'm not sure whether there can be more more than one such
ElementToBeChanged, or whether there can be more than one RenderAs
under a particular ElementToBeChanged but I'll assume that there can
be one or the other, in which case you need to iterate over the
RenderAs elements with an xsl:for-each. You're going to need to add
attributes to the input element with this xsl:for-each, so it needs to
go within the input element that you're creating; I'd put it after the
xsl:copy-of where you copy the existing attributes, in case you want
to override any of them:

<xsl:template match="input">
  <xsl:copy>
    <xsl:copy-of select="@*" />
    <xsl:for-each select="key('changes', @id)/RenderAs">
      ...
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

Then it's a matter of making the relevant change. You showed three
possibilities in your example, where the RenderAs element was
'HIDDEN', 'READONLY' or 'NORMAL'. Since these possibilities are
mutually exclusive (any one RenderAs can only have one of those
values), you need an xsl:choose to decide between them. Based on the
code that you gave, this would look like:

<xsl:template match="input">
  <xsl:copy>
    <xsl:copy-of select="@*" />
    <xsl:for-each select="key('changes', @id)/RenderAs">
      <xsl:choose>
        <xsl:when test=". = 'HIDDEN'">
          <xsl:attribute name="visibility">hidden</xsl:attribute>
        </xsl:when>
        <xsl:when test=". = 'READONLY'">
          <xsl:attribute name="read-only">true</xsl:attribute>
        </xsl:when>
        <xsl:when test=". = 'NORMAL'">
          <xsl:attribute name="visibility">visible</xsl:attribute>
          <xsl:attribute name="read-only">false</xsl:attribute>
        </xsl:when>
      </xsl:choose>
    </xsl:for-each>
  </xsl:copy>
</xsl:template>

If there are several form elements that you want to be treated in this
way, just add them to the list of elements held in the match attribute
of the above template. Note that if any of them have content, you'll
need to add an <xsl:apply-templates /> instruction after the
xsl:for-each in the above, so that their content is copied using the
identity template.

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread