FloD
FloD

Reputation: 27

How to match identifiers ?

I have a problem matching different identifiers in an XSL transformation from XML to HTML. I have a long list of link elements, like this :

<link target="#E_1 #FCB_1 #FWH_2 #FWH_3">

Each text beginning with # in the target attribute matches an xml:id attribute within a paragraph element like this :

<p xml:id="E_1">text text text</p>
<p xml:id="FCB_1">text text text</p>
<p xml:id="FWH_2">text text text</p>
<p xml:id="FWH_3">text text text</p>

What I need to do is to create a div element for each target attribute, meaning to obtain the following :

<div class="impair">
<div>
<p>Content of the paragraph with xml:id equal to "E_1"</p>
</div>
<div>
<p>Content of the paragraph with xml:id equal to "FCB_1"</p>
</div>
<div>
<p>Content of the paragraph with xml:id equal to "FWH_2"</p>
</div>
<div>
<p>Content of the paragraph with xml:id equal to "FWH_3"</p>
</div>
</div>

I have tried several things with xsl:variable, xsl:param, xsl:key or functions like starts-with or even substring but nothing works fine for now. So I am asking for help. I am still trying to improve my XSL skills... Many thanks in advance for your help. Flo

Upvotes: 0

Views: 288

Answers (2)

Tim C
Tim C

Reputation: 70648

If you are looking for an XSLT 1.0 solution, here is one possible method, which converts the target attribute to the string #E_1#FCB_1#FWH_2#FWH_3# and uses various string functions to select the p elements whose id occurs in that string.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:template match="/">
      <xsl:apply-templates select="//link" />
  </xsl:template>

  <xsl:template match="link">
    <xsl:variable name="target" select="concat(translate(@target, ' ', ''), '#')" />
    <xsl:value-of select="$target" />
    <div class="impair">
      <xsl:apply-templates select="//p[contains($target, concat('#', @xml:id, '#'))]">
        <xsl:sort select="string-length(substring-before($target, concat('#', @xml:id, '#')))" />
      </xsl:apply-templates>
    </div>
  </xsl:template>

  <xsl:template match="p">
    <div>
      <p><xsl:value-of select="." /></p>
    </div>
  </xsl:template>
</xsl:stylesheet>

This would not work if you had repeated ids in the target attribute though, like #E_1 #FCB_1 #FWH_3 #FWH_3. In this case, a recursively called named-template could work to split up the target attribute:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:key name="p" match="p" use="concat('#', @xml:id)" />

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

  <xsl:template match="link">
    <div class="impair">
        <xsl:call-template name="split">
          <xsl:with-param name="string" select="@target" />
        </xsl:call-template>
    </div>
  </xsl:template>

  <xsl:template name="split">
    <xsl:param name="string" />
    <xsl:if test="$string != ''">
      <xsl:apply-templates select="key('p', substring-before(concat($string, ' '), ' '))" />
      <xsl:if test="contains($string, ' ')">
        <xsl:call-template name="split">
          <xsl:with-param name="string" select="substring-after($string, ' ')" />
        </xsl:call-template>
      </xsl:if>
    </xsl:if>
  </xsl:template>

  <xsl:template match="p">
    <div>
      <p><xsl:value-of select="." /></p>
    </div>
  </xsl:template>
</xsl:stylesheet>

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167716

Assuming an XSLT 2.0 processor you can use the id function with the tokenized values from the target attribute:

<xsl:template match="link[@target]">
    <div class="impair">
        <xsl:apply-templates select="id(for $idref in tokenize(@target, '\s+') return substring($idref, 2))" mode="wrap"/>
    </div>
</xsl:template>

<xsl:template match="p" mode="wrap">
    <div>
        <xsl:copy>
            <xsl:apply-templates/>
        </xsl:copy>
    </div>
</xsl:template>

Upvotes: 1

Related Questions