Reputation: 45
I've to figure out if I'm looking at a href id for the first time or not.
I've the following links:
<a href="some_fn_1">link1</a>
<a href="some_fn_2">link2</a>
<a href="some_fn_1">link1</a>
Let's just say href atrribute value is an id
I've to transform these links to following using xsl:
<fn id="some_fn_1">
link1
</fn>
<fn href="some_fn_2">
link2
</fn>
<xref href="some_fn_1"/>
So what I've to basically do is iterate through all 'a' node with href across the document and remember if I've encountered a href to a particular id once. Generate a node for the first href and if another href points to the same Id, I've to generate an xref. So I've to remember the previously visited href id's. I've tried the following:
<xsl:param name="processed-footnotes"/>
<xsl:template match="//a[contains(@href, '_fn_')]">
<xsl:param name="processed-footnotes" select="concat($processed-footnotes, concat(concat('~', substring-after(@href,'/')), '~'))"/>
<xsl:variable name="fn-href">
<xsl:value-of select="@href"/>
</xsl:variable>
<xsl:variable name="fn-id">
<xsl:value-of select="substring-after($fn-href,'/')"/>
</xsl:variable>
<xsl:call-template name="process-footnotes">
<xsl:with-param name="fn-href" select="$fn-href"/>
<xsl:with-param name="fn-id" select="$fn-id"/>
</xsl:call-template>
</xsl:template>
I intended to use $processed-footnotes as the global param which I can update for each href I find but reassigning or updating a value to param or variable isn't possible in xsl.
I'm clueless how to achieve this using xsl now. Please help.
Upvotes: 0
Views: 199
Reputation: 167561
The key based solution has already been suggested and is fine, perhaps a formulation in XSLT 2/3 not having to use the count
or generate-id
based comparison to check for node identity is helpful to understand the approach, furthermore I prefer to delegate the transformation to templates, so here is a slightly different way:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:key name="group" match="a[contains(@href, '_fn_')]" use="@href"/>
<xsl:template match="a[contains(@href, '_fn_')][. is key('group', @href)[1]]">
<fn id="{@href}">
<xsl:apply-templates/>
</fn>
</xsl:template>
<xsl:template match="a[contains(@href, '_fn_')][not(. is key('group', @href)[1])]">
<xref href="{@href}"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6rexjhG/1
Downgraded to XSLT 1 this would become
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:key name="group" match="a[contains(@href, '_fn_')]" use="@href"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a[contains(@href, '_fn_')][generate-id() = generate-id(key('group', @href)[1])]">
<fn id="{@href}">
<xsl:apply-templates/>
</fn>
</xsl:template>
<xsl:template match="a[contains(@href, '_fn_')][generate-id() != generate-id(key('group', @href)[1])]">
<xref href="{@href}"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6rexjhG
In XSLT 3 using an accumulator is also an alternative that would even work with streaming:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy" streamable="yes" use-accumulators="link-id-counts"/>
<xsl:accumulator name="link-id-counts" as="map(xs:string, xs:integer)" initial-value="map{}" streamable="yes">
<xsl:accumulator-rule
match="a[contains(@href, '_fn_')]"
select="map:put($value, string(@href), ($value(@href), 0)[1] + 1)"/>
</xsl:accumulator>
<xsl:template match="a[contains(@href, '_fn_')][accumulator-before('link-id-counts')(@href) = 1]">
<fn id="{@href}">
<xsl:apply-templates/>
</fn>
</xsl:template>
<xsl:template match="a[contains(@href, '_fn_')][accumulator-before('link-id-counts')(@href) > 1]">
<xref href="{@href}"/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/6rexjhG/2
Upvotes: 0
Reputation: 116993
Consider the following example:
XML
<input>
<a href="some_fn_1">link1</a>
<a href="some_fn_2">link2</a>
<a href="some_fn_1">link1</a>
</input>
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:key name="a" match="a" use="@href" />
<xsl:template match="/input">
<output>
<xsl:for-each select="a">
<xsl:choose>
<xsl:when test="count(. | key('a', @href)[1]) = 1">
<!-- this is the first occurrence of this href value -->
<fn id="{@href}">
<xsl:value-of select="."/>
</fn>
</xsl:when>
<xsl:otherwise>
<xref href="{@href}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</output>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<output>
<fn id="some_fn_1">link1</fn>
<fn id="some_fn_2">link2</fn>
<xref href="some_fn_1"/>
</output>
For better understanding how this works, read: http://www.jenitennison.com/xslt/grouping/muenchian.html
Upvotes: 2