Björn
Björn

Reputation: 585

XSLT: Setting global variable to attribute value

General I'm trying to modify an XML that includes JavaScript. I want to add an extra top hierarchial level if the JavaScript is referencing one of the page top levels.

Specifics I want to find the new top level (formName) and then add it to the scripts if they have assignments that start with a page top level (pageNames and subPageNames). I have tried to run this in XMLSpy, but I can't even populate variable formName. What is the best way of solving this?

My example XML:

<?xml version="1.0" encoding="UTF-8"?>
<template>
  <subform name="formABC">
    <pageSet>
        <pageArea name="Page1">
            <subform>
                <field>
                    <event>
                        <script>this.rawValue = SubPageA.subhuvud.namn.rawValue;</script>
                    </event>
                </field>
                <field>
                    <event>
                        <script>this.rawValue = not_this.rawValue;</script>
                    </event>
                </field>
            </subform>
        </pageArea>
        <pageArea name="Page2">
            <field>
                <event>
                    <script>this.rawValue = SubPageK.subhuvud.namn.rawValue;</script>
                </event>
            </field>
            <area>
                <field>
                    <event>
                        <script>this.rawValue = Page1.subhuvud.namn.rawValue;</script>
                    </event>
                </field>
                <field>
                    <event>
                        <script>this.rawValue = other.rawValue;</script>
                    </event>
                </field>
            </area>
        </pageArea>
    </pageSet>
    <subform name="SubPageA">
    </subform>
    <subform name="SubPageK">
    </subform>
  </subform>
</template>

My current XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common" extension-element-prefixes="exsl"
xmlns:xdp="http://ns.adobe.com/xdp/">
<xsl:output omit-xml-declaration="yes" indent="yes"/>

<xsl:variable name="formName"     select="/xdp:xdp/template/subform/@name"/>
<xsl:variable name="pageNames"    select="/xdp:xdp/template/subform/subform/@name"/>
<xsl:variable name="subPageNames" select="/xdp:xdp/template/subform/pageSet/pageArea/@name"/>
<xsl:variable name="allPageNames" select="concat($pageNames, $subPageNames)"/>

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

<!--Rewite Javascript-->
<!--Insert formname in front of page that is first.-->
<xsl:template match="//script">
    <xsl:variable name="match" select="exsl:node-set($allPageNames)[contains(current(), .)]"/>
    <xsl:choose>
        <xsl:when test="$match">                                                <!--this.rawValue = Page1.subhuvud.namn.rawValue;-->
            <xsl:value-of select="substring-before(., $match)"/>                <!--this.rawValue = -->
            <xsl:value-of select="$formName"/>                                  <!--formABC-->
            <xsl:value-of select="'.'"/>                                        <!--.-->
            <xsl:value-of select="$match"/>                                     <!--Page1-->
            <xsl:value-of select="substring-after(., $match)"/>                 <!--.subhuvud.namn.rawValue;-->
        </xsl:when>                                                             <!--this.rawValue = formABC.Page1.subhuvud.namn.rawValue;-->
        <xsl:otherwise>
            <xsl:copy/>
            <xsl:apply-templates select="node()|@*"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
</xsl:stylesheet>

Output that I seek:

<?xml version="1.0" encoding="UTF-8"?>
<template>
 <subform name="formABC">
    <pageSet>
        <pageArea name="Page1">
            <subform>
                <field>
                    <event>
                        <script>this.rawValue = formABC.SubPageA.subhuvud.namn.rawValue;</script>
                    </event>
                </field>
                <field>
                    <event>
                        <script>this.rawValue = not_this.rawValue;</script>
                    </event>
                </field>
            </subform>
        </pageArea>
        <pageArea name="Page2">
            <field>
                <event>
                    <script>this.rawValue = formABC.SubPageK.subhuvud.namn.rawValue;</script>
                </event>
            </field>
            <area>
                <field>
                    <event>
                        <script>this.rawValue = formABC.Page1.subhuvud.namn.rawValue;</script>
                    </event>
                </field>
                <field>
                    <event>
                        <script>this.rawValue = other.rawValue;</script>
                    </event>
                </field>
            </area>
        </pageArea>
    </pageSet>
    <subform name="SubPageA">
    </subform>
    <subform name="SubPageK">
    </subform>
  </subform>
  </template>

Upvotes: 1

Views: 3013

Answers (1)

John Bollinger
John Bollinger

Reputation: 180978

With regard to binding variables' values, I expand on my comment by pointing out that your select expressions rooted at /xdp:xdp will never match anything in your example input file, thus values of the associated variables will always be empty node sets. You should be able to fix that (with respect to the example input) simply by removing that element:

<xsl:variable name="formName" select="/template/subform/@name"/>

Next, I observe that the mechanism by which you form the value for variable allPageNames cannot work because concat() requires string arguments, but you are trying to provide node sets. Nor would converting the node sets to strings do the right thing, because

  1. Converting a node set to a string gets you the string value of the first node (only)
  2. You're trying to form an undelimited concatenation, from which you would be unable afterward to extract the original strings.
  3. Where you actually use the value, you convert it to a node set, so wouldn't it be better to just generate the value as a node set in the first place?

XPath has a node-set union operator, |, which you can use in the expression for this variable's value. For example,

<xsl:variable name="pageNames"    select="/template/subform/subform/@name"/>
<xsl:variable name="subPageNames" select="/template/subform/pageSet/pageArea/@name"/>
<xsl:variable name="allPageNames" select="$pageNames | $subPageNames"/>

Now you don't need EXSL (for what you present in the question), because allPageNames is already a node set.

But that's not enough: you have a problem with variable match in the script template. The select expression in its assignment will not work, because contains() is another string function, and you are attempting to pass a node set to it. Moreover, where it is inside the predicate, it will evaluate to the node being tested, not, as it appears you want, the <script> element being transformed. In any case, the test you seem to be trying to perform is prone to false positives.

I suggest a different approach. First, identify the name of the object you want to test. This looks like this might be consistent with your objective as you describe it, when processed with the script element as the context node:

<xsl:variable name="topObject"
    select="normalize-space(substring-before(concat(substring-after(., '='), '.'), '.'))"/>

Having done that you can test whether the selected name is among those described by the $allPageNames node set via this test expression: $allPageNames[. = $topObject]. Overall, the script template might look like this:

<xsl:template match="script">
  <!-- assumed: this element has text-only content -->
  <xsl:variable name="topObject"
      select="normalize-space(substring-before(concat(substring-after(., '='), '.'), '.'))"/>
  <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:choose>
      <xsl:when test="$allPageNames[. = $topObject]">
        <xsl:value-of select="substring-before(., $topObject)"/>
        <xsl:value-of select="$formName"/>
        <xsl:text>.</xsl:text>
        <xsl:value-of select="$topObject"/>
        <xsl:value-of select="substring-after(., $topObject)"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:apply-templates select="text()"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:copy>
</xsl:template>

Upvotes: 1

Related Questions