1

If an anchor <a class="section-ref"> has an @href value that links to an @id of a <div class="sect1"> which is a child of a <div class="chunk">, I want to change the value of the anchor @href to the @id of the parent chunk. I'm unsure how to find the @id of the parent chunk through XSLT/XPath.

XML:

<book>
<div>
    <p>In the <a class="section-ref" href="#i2398" id="i2397">next section</a>, we turn lorem ipsum.</p>
</div>
<div class="extend" id="i100949">
    <div class="check" id="i100950">
        <h1 class="title">Check</h1>
        <a class="other-ref" href="folder/other-ref.xml" id="i100953"></a>
    </div>
</div>
<div class="chunk" id="i100954">
    <h1 class="title">8.4</h1>
    <div class="other-section" id="i100955">
        <p id="i100956"> Lorem Ipsum</p>
    </div>
    <div class="sect1" id="i2398">
        <h1 class="title">Lorem Ipsum</h1>
        <p>Blah blah blah</p>
    </div>
</div>

XSLT:

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

<!--Change section-ref @href to parent chunk @id if they are @ids of a sect1-->

<xsl:template match="a[@class='section-ref']">
    <xsl:variable name="section-id" select="substring-after(./@href, '#')"/>
    <xsl:variable name="section" select="$section-id = //div[@class='chunk']/div[@class='sect1']/@id"/>

    <xsl:choose>
        <xsl:when test="$section">
            <xsl:copy>
                <xsl:attribute name="href" select="NEED XPATH HERE"/>
                <xsl:apply-templates select="(node() | @*) except @href"/>
            </xsl:copy>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy>
                <xsl:apply-templates select="node() | @*"/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

Desired output:

<book>
<div>
    <p>In the <a class="section-ref" href="i100954" id="i2397">next section</a>, we turn lorem ipsum.</p>
</div>
<div class="extend" id="i100949">
    <div class="check" id="i100950">
        <h1 class="title">Check</h1>
        <a class="other-ref" href="folder/other-ref.xml" id="i100953"/>
    </div>
</div>
<div class="chunk" id="i100954">
    <h1 class="title">8.4</h1>
    <div class="other-section" id="i100955">
        <p id="i100956"> Lorem Ipsum</p>
    </div>
    <div class="sect1" id="i2398">
        <h1 class="title">Lorem Ipsum</h1>
        <p>Blah blah blah</p>
    </div>
</div>

Note that the section-ref anchor @href value changes to the @id of the parent chunk (i100954):

<p>In the <a class="section-ref" href="i100954" id="i2397">next section</a>, we turn lorem ipsum.</p>

3 Answers 3

1

I would define a key to reference the div and then you can select the parent div's id:

  <xsl:key name="ref" match="div[@class = 'sect1']" use="@id"/>

  <xsl:template match="a[@class = 'section-ref' and key('ref', substring(@href, 2))]/@href">
      <xsl:attribute name="{name()}" select="'#' || key('ref', substring(., 2))/parent::div/@id"/>
  </xsl:template>

https://xsltfiddle.liberty-development.net/jyH9rM8 has a sample, it is XSLT 3 but for XSLT you just need to use concat instead of the || operator to construct the new attribute value and then you need to use the identity transformation template (you already have) instead of the xsl:mode.

Sign up to request clarification or add additional context in comments.

3 Comments

This is great, thanks Martin. I'm trying to unpack this, what is the 2 used for in the substring?
The href values starts with a hash, like href="#i2398" while the id used for the key doesn't have the hash so to use the href to reference the id you need to remove the first character of the href. That is all what the substring(@href, 2) or (in the context of the attribute) the substring(., 2) does, it takes the href value without the leading hash.
Thank you for the explanation. I had no idea I could use a number to indicate the substring, so I was using substring-after to take out the #.
1

To fill the placeholder "NEED XPATH HERE" of your example, you should use

../../../div[@class='chunk']/@id

or in a whole expression:

<xsl:attribute name="href" select="../../../div[@class='chunk']/@id" />

This gives you the desired result.

1 Comment

Thanks. While this works for the above example, it won't work for every type of content due to changing of nested divs. I'm sorry I didn't explicitly state that in the question text.
0
<xsl:template match="@*|node()">
       <xsl:copy>
           <xsl:apply-templates select="@*"/>
       </xsl:copy>
   </xsl:template>
    <xsl:template match="book">
        <xsl:element name="book">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="div">
        <xsl:element name="div">
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="p">
        <xsl:element name="p">
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="h1">
        <xsl:element name="h1">
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="a[@class='section-ref']">
        <xsl:element name="a">
        <xsl:attribute name="href">
            <xsl:value-of select="ancestor::div/following-sibling::div[@class='chunk']/@id"/>
        </xsl:attribute>
            <xsl:apply-templates select="@*"/>
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>

I have tried to get your output in simple way

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.