Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

If you're just looking to replace the xref with the corresponding figure, you can match up the @linkend against the @id.

Example:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="xref">
        <xsl:apply-templates select="//figure[@id=current()/@linkend]"/>
    </xsl:template>

</xsl:stylesheet>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top