I am looking for a way to copy any referenced figure/node to where it is referenced.
<chapter id="intro">
<title>Introduction</title>
<para>Welcome to our new product. One of its
new features is a <xref linkend="some-figure"/>.
Other new features include ...
</para>
<para>Grab that <xref linkend="some-figure"/> and pull it here too.</para>
</chapter>
<chapter>
<title>Some other chapter</title>
<para>This chapter contains the figure!
<figure id="some-figure"><title>my figure...</title><mediaobject>...</mediaobject></figure>
</para>
</chapter>
Can this be turned into:
<chapter id="intro">
<title>Introduction</title>
<para><figure><title>my figure...</title><mediaobject>...</mediaobject></figure>
Welcome to our new product. One of its
new features is a <xref linkend="some-figure"/>.
Other new features include ...
</para>
<para><figure><title>my figure...</title><mediaobject>...</mediaobject></figure>
Grab that <xref linkend="some-figure"/> and pull it here too.
</para>
</chapter>
<chapter>
<title>Some other chapter</title>
<para>This chapter contains the figure!
<figure id="some-figure"><title>my figure...</title><mediaobject>...</mediaobject></figure>
</para>
</chapter>
Updating the references to point to the copied figures would be icing on the cake, but I would like information regarding and way at all to get the nodes copied to where they are referenced.
So given any para
that contains an xref
, you want to copy the linked figures (minus their id
attribute) into the start of the para
content. I would define a key for fast access to the figure
elements by id:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="figureById" match="figure" use="@id" />
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()" /></xsl:copy>
</xsl:template>
<xsl:template match="para[.//xref]">
<xsl:copy>
<xsl:apply-templates select="@*" /><!-- may not be necessary -->
<!-- copy in referenced figures -->
<xsl:apply-templates select="key('figureById', .//xref/@linkend)"
mode="noid"/>
<!-- and continue with child nodes as normal -->
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!-- special almost-identity template to remove the id attribute -->
<xsl:template match="node()" mode="noid">
<xsl:copy>
<xsl:apply-templates select="@*[local-name() != 'id']|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This makes use of a nice feature of the key
function that if you pass it a node set as its second argument (the key values to look up) then the result is the union of the node sets that result from looking up each key value in turn. So key('figureById', xref/@linkend)
gives you all the figure elements whose id
matches any of the xref
elements in this para, but if the same figure is referenced more than once you only get one copy of the figure inserted.
Updating the references to point to the copied figures would be icing on the cake
You could achieve this by rewriting the IDs of copied figures to include the generate-id()
of the target paragraph, and then use the same transformation on the xref
linkends. Something like this:
<xsl:template match="para[.//xref]">
<xsl:copy>
<xsl:apply-templates select="@*" /><!-- may not be necessary -->
<!-- copy in referenced figures, modifying their ids to include our own -->
<xsl:apply-templates select="key('figureById', .//xref/@linkend)"
mode="modify-id">
<xsl:with-param name="targetNode" select="." />
</xsl:apply-templates>
<!-- and continue with child nodes as normal -->
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="node()" mode="modify-id">
<xsl:param name="targetNode" />
<xsl:copy>
<xsl:apply-templates select="@*"/>
<xsl:attribute name="id">
<xsl:value-of select="concat(generate-id($targetNode), '-', @id)" />
</xsl:attribute>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!-- munge linkend attributes in the same way we did for copied figure ids -->
<xsl:template match="para//xref/@linkend">
<xsl:attribute name="linkend">
<xsl:value-of select="concat(generate-id(ancestor::para[1]), '-', .)" />
</xsl:attribute>
</xsl:template>
On my system, using xsltproc
(and wrapping your example in a root tag to make it well formed), this produces
<?xml version="1.0"?>
<root>
<chapter id="intro">
<title>Introduction</title>
<para><figure id="idp1744-some-figure"><title>my figure...</title><mediaobject>...</mediaobject></figure>Welcome to our new product. One of its
new features is a <xref linkend="idp1744-some-figure"/>.
Other new features include ...
</para>
<para><figure id="idp2656-some-figure"><title>my figure...</title><mediaobject>...</mediaobject></figure>Grab that <xref linkend="idp2656-some-figure"/> and pull it here too.</para>
</chapter>
<chapter>
<title>Some other chapter</title>
<para>This chapter contains the figure!
<figure id="some-figure"><title>my figure...</title><mediaobject>...</mediaobject></figure>
</para>
</chapter>
</root>
The exact form of the generated IDs (the idpNNNN
in this example) will vary from processor to processor but they're guaranteed to be unique and consistent.