Monday, 23 January 2012

Copying an XML document, with changes

I needed to modify an XML document (well, strictly an XMI document produced by ArgoUML). An XSLT stylesheet did the job.

It'd be a Bad Idea if the XMI did cross-referencing using names. There's no modelling reason why there should be only one element with a given name; for example, you might have lots of classes with methods named Print or attributes named Status, and indeed a single class might have more than one method with the same name (but, one hopes, different signatures).

The way XMI gets over this is that an element's definition has an attribute xmi.id, whose value is a long and hopefully unique string, and references to it from other elements have an attribute xmi.idref whose value is the same string.

The problem I have is that I need elements' names, which ArgoUML itself manages by chasing the xmi.idref to xmi.id "pointer" and reading the name attribute from the definition. This would be fine if I was writing in XSLT - I could use <xsl:key> - but I plan to write in Ada using XPath In Ada.

What I'm going to do is to preprocess the XMI file. Every time I find an xmi.idref attribute, I'm going to add a name attribute containing the name of the referenced element. (In case you were worried, I'll be making a copy with the changes in!)

First off, specify the encoding etc (this was inserted for me by Emacs):

<?xml version="1.0" encoding="utf-8"?>

Then, start the stylesheet. The UML namespace spec ought to be a proper URL, but this is the one used by ArgoUML. To be fair, it's apparently the one specified by OMG for UML 1.4.

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:UML="org.omg.xmi.namespace.UML"
  version="1.0">

Specify details of the output:

  <xsl:output method="xml" encoding="iso-8859-1" indent="yes"/>
  <xsl:strip-space elements="*"/>

The magical "identity rule", which copies the entire document, elements, attributes, processing instructions and all, except where the stylesheet overrides it:

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

Now for the interesting bit! This template matches any element with an xmi.idref attribute:

  <xsl:template match="UML:*[@xmi.idref]">

I save the reference in a variable:

    <xsl:variable name="idref" select="@xmi.idref"/>

Now we apply an xsl:copy template much like the one in the identity rule above, but this time inserting a name attribute whose value is copied from the element with the matching xmi.id:

    <xsl:copy>
      <xsl:attribute name="name">
        <xsl:value-of select="//UML:*[@xmi.id=$idref]/@name"/>
      </xsl:attribute>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>

Close the template:

  </xsl:template>

Finally, wrap:

</xsl:stylesheet>

Further work

This code repetitively searches the whole document for xmi.id. I suspect this is a case for using xsl:key!

No comments:

Post a Comment