Re: How to remove duplicates nodes?

Subject: Re: How to remove duplicates nodes?
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Sat, 26 Aug 2000 14:04:02 +0100
Joel,

>The only way to compute a unique value of a Context (almost a 
>hashcode if you will) is by looking at all the ObjectReference
>elements of the Context and concatenating all their attributes
>together (perhaps by including spaces):  @objectID, @attributeGroupID,
>@instanceID, and @attributeID.

Hmm.  It isn't easy within XPath to work with strings where you don't know
how long they are - you have to use recursion to parse them or create them,
which means using named templates.  But you can't call named templates from
within the 'use' attribute of xsl:key, which means that you're fairly
limited with the keys that you can put together, unless you start getting
into extension functions.

Do you have the freedom to change the XML source format?  From the
description of the underlying data that you've given here, it would perhaps
make more sense to have uniquely identified contexts:

<Context id="532">
  <ObjectReference objectID="152"
                   attributeGroupID="153"
                   instanceID="0"
                   attributeID="154"/>
</Context>

and ContextReferences to them:

<ContextReference contextID="532" />

in the same way as you have ObjectReferences.

If you can't, then the solution will involve a mode that creates a unique
identifier for the Context:

<xsl:template match="Context" mode="generate-id">
  <xsl:for-each select="ObjectReference">
    <xsl:for-each select="@*">
      <xsl:value-of select="." />
      <xsl:if test="position() != last()">,</xsl:if>
    </xsl:for-each>
    <xsl:if test="position() != last()">;</xsl:if>
  </xsl:for-each>
</xsl:template>

You then have to go through each of the Contexts to see if it is unique.
Checking whether it's unique involves checking each of the preceding
Contexts within the RuleMapping to see if any of them have the same
generated id as the current one.  Because the generated id is being done
through a template, you can't use XPath for this, you have to do it by hand.

<xsl:template match="RuleMapping">
  Rule Mapping: <xsl:value-of select="@name" />
  <xsl:for-each select="Rule/RHS/Context">
    <xsl:variable name="this-context-position" select="position()" />
    <xsl:variable name="this-context-id">
      <xsl:apply-templates select="." mode="generate-id" />
    </xsl:variable>
    <xsl:variable name="preceding">
      <xsl:for-each
select="ancestor::RuleMapping/Rule/RHS/Context[following::Context[generate-i
d() = generate-id(current())]]">
        <xsl:variable name="test-context-id">
          <xsl:apply-templates select="." mode="generate-id" />
        </xsl:variable>
        <xsl:if test="normalize-space($test-context-id) =
normalize-space($this-context-id)">match</xsl:if>
      </xsl:for-each>
    </xsl:variable>
    <xsl:if test="not(normalize-space($preceding))">
      UNIQUE CONTEXT: <xsl:copy-of select="." />
    </xsl:if>
  </xsl:for-each>
</xsl:template>

This is really messy and will take a lot of processing power for large
source documents, but it seems to work with the example that you gave in
SAXON and Xalan-C++.

I hope that helps,

Jeni

Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread