Domanda

I'm trying to use XSLT 2.0 to replace an arbitrary number of substrings. Let's say my XML looks like this:

<scheme_template id="job"
    use_when_ref="action">
    <![CDATA[
    <p>
        @job.fluff@  Haul is based on 
        <b>@job.stat@:</b>
    </p>
    <table class="scheme_job_table">
        <tr>
            <td>2 or less</td>
            <td>@job.low@</td>
        </tr>
        <tr>
            <td>3-5</td>
            <td>@job.middle@</td>
        </tr>
        <tr>
            <td>6 or more</td>
            <td>@job.high@</td>
        </tr>
    </table>
    ]]>
</scheme_template>

<scheme name="JOB! CHICKEN SOUP FOR THE SOULLESS"
    copies="1">
    <use_scheme_template id_ref="job">
        <property id="job.fluff">
            Chose 1 of your monsters to make and sell 
            heart-warming books of life-affirming awwwww.
        </property>
        <property id="job.stat">Smart</property>
        <property id="job.low">$4</property>
        <property id="job.middle">$6</property>
        <property id="job.high">$8</property>
    </use_scheme_template>
</scheme>

I'd like to use an XSL transform to put all the "property" values into the scheme template. My (faulty) XSL looks like this:

<xsl:template match="use_scheme_template" mode="expand_template">
    <xsl:param name="template_id" select="@id_ref"/>
    <xsl:param name="base_text" select="//scheme_template[@id=$template_id]"/>
    <xsl:variable name="expanded_text">
        <xsl:apply-templates select="property" mode="replace_props">
            <xsl:with-param name="base_text" select="$base_text"/>
        </xsl:apply-templates>
    </xsl:variable>
    <xsl:value-of select="$expanded_text" disable-output-escaping="yes" />
</xsl:template>

<xsl:template match="property" mode="replace_props">
    <xsl:param name="base_text"/>
    <xsl:value-of select="replace($base_text, concat('@', @id, '@'), text())"/>
</xsl:template>

But this only replaces the first property.

What do I need to do to run replace an arbitrary number of times on the same string?

È stato utile?

Soluzione

I would consider approaching this with xsl:analyze-string instead of replace. The following will search for @anything@ substrings within the text and replace them with the matching property value (if such a property exists), leaving the rest of the text unchanged:

<xsl:template match="use_scheme_template" mode="expand_template">
    <xsl:param name="template_id" select="@id_ref"/>
    <xsl:param name="base_text" select="//scheme_template[@id=$template_id]"/>

    <xsl:variable name="properties" select="property" />
    <xsl:variable name="expanded_text">
      <xsl:analyze-string select="$base_text" regex="@(.*?)@">
        <xsl:matching-substring>
          <!-- substitute the matching property value, if there is one,
               or leave untouched if not -->
          <xsl:value-of select="($properties[@id = regex-group(1)], .)[1]" />
        </xsl:matching-substring>
        <xsl:non-matching-substring>
          <!-- leave non-matching parts unchanged -->
          <xsl:value-of select="." />
        <xsl:non-matching-substring>
      </xsl:analyze-string>
    </xsl:variable>
    <xsl:value-of select="$expanded_text" disable-output-escaping="yes" />
</xsl:template>

Altri suggerimenti

Instead of applying the replace_props template to all properties initially, consider just applying it to the first property:

   <xsl:apply-templates select="property[1]" mode="replace_props">
        <xsl:with-param name="base_text" select="$base_text"/>
    </xsl:apply-templates>

Then, make the actual replace_props template recursive. The logic would then be to create a variable with the current replaced text in. You would then check if there is a following property element available. If so, call the template recursively using the newly replaced text, otherwise output the text:

Try this to start...

<xsl:template match="property" mode="replace_props">
    <xsl:param name="base_text"/>
    <xsl:variable name="expanded_text" select="replace($base_text, concat('@', @id, '@'), text())"/>
    <xsl:choose>
        <xsl:when test="following-sibling::property">
            <xsl:apply-templates select="following-sibling::property[1]" mode="replace_props">
                <xsl:with-param name="base_text" select="$expanded_text"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$expanded_text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

... But when you run it you may find the "job.low", "job.middle" and "job.high" values come out empty. This is because the $ symbol has special meaning in the replace function (The second argument of replace can use regular expressions, and a $ in the second parameter can be used to output the value of matching patterns).

This means you will need to do some extra work to escape the $ symbol. Try this template instead

<xsl:template match="property" mode="replace_props">
    <xsl:param name="base_text"/>
    <xsl:variable name="text" select="replace(text(), '\$', '\\\$')" />
    <xsl:variable name="expanded_text" select="replace($base_text, concat('@', @id, '@'), $text)"/>
    <xsl:choose>
        <xsl:when test="following-sibling::property">
            <xsl:apply-templates select="following-sibling::property[1]" mode="replace_props">
                <xsl:with-param name="base_text" select="$expanded_text"/>
            </xsl:apply-templates>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$expanded_text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top