XTM-Topic Maps über XMI aus UML-Modellen generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten Ihr bevorzugtes XMI-fähiges UML-Modellierungswerkzeug verwenden, um XTM-Topic Maps zu erstellen.

Lösung

Wenn Sie zu den Lesern gehören, die noch nicht mit Topic Maps vertraut sind, dann sollten Sie vielleicht erst den Abschnitt »Diskussion« lesen, bevor Sie sich diese Lösung genauer anschauen.

Da UML nicht explizit für die Darstellung von Topic Maps entworfen wurde, funktioniert dieses Beispiel nur dann, wenn bestimmte Konventionen eingehalten werden. Die meisten Konventionen drehen sich um den Einsatz spezieller UML-Stereotypen. Ein Stereotyp ist ein UML-Erweiterungsmechanismus, mit dem Sie einen beliebigen UML-Bezeichner mit einem Symbol verbinden können, das angibt, dass dieser Bezeichner eine benutzerdefinierte Semantik hat. Um Topic Maps in UML zu implementieren, können Sie die Stereotypen verwenden, die in der folgenden Tabelle aufgelistet sind.

Tabelle: Konventionen bei der Erstellung von Topic Maps in UML.

Stereotyp UML-Kontext Bedeutung
Topic Klasse Steht für ein Topic. Weil das laut unseren Konventionen die vorgegebene Rolle einer Klasse ist, ist dieser Stereotyp optional.
Subject Klasse Gibt an, dass die Klasse eine Beschreibung des Aussagegegenstands modelliert. Kennzeichnet die Klasse als Aussagegegenstand, wenn sie das Ziel einer subjectIdentityRef ist. Insbesondere macht dieser Stereotyp den subjectIndicator eindeutig, der auf eine veröffentlichte Beschreibung eines Aussagegegenstandes statt auf ein Topic oder eine Ressource verweist. Siehe XML Topic Maps (XTM) 1.0: subjectIdentity-Element.
Resource Klasse Gibt an, dass die Klasse eine Ressource darstellt, die als Ziel einer Topic-Belegstelle-Beziehung benutzt wird.
Base Name Klasse Gibt an, dass die Klasse eine alternative Topic-Benennung modelliert, und impliziert deswegen einen Gültigkeitsbereich. Die Elemente des Gültigkeitsbereichs werden mit der Klasse baseName über Beziehungen mit dem Stereotyp scope assoziiert.
Occurrence Assoziation Gibt an, dass die Beziehung auf eine Ressource zeigt, die eine Belegstelle zu dem Topic ist.
Scope Assoziation Gibt die Beziehung an, die den Gültigkeitsbereich des Topic Map-Merkmals angibt. Die Natur des Gültigkeitsbereichs hängt vom Stereotyp der Zielklasse ab (topicRef, resourceRef oder subjectIndicatorRef).
Variant Generalisierungs-assoziation und Klasse Stellt über eine Generalisierungsassoziation mit dem Stereotyp Variant eine Verbindung zu dem Topic her, dessen Variante es ist. Die Parameter, die die Anwendbarkeit der Variante bestimmen, sind in den Klassenattributen kodiert. Die Klasse selbst bekommt ebenfalls das Stereotyp Variant, um sie von einem Topic zu unterscheiden.

Zusätzlich zu diesen Stereotypen werden noch die folgenden Abbildungen von UML auf Topic Maps verwendet:

UML-Klasse

Modelliert ein Topic Map-Topic, sofern kein nicht-topic-Stereotyp vorhanden ist. Der Klassenname wird als Benennung des Topics verwendet. Falls die Klasse ein Attribut namens ID hat, wird dessen Vorgabewert als Topic-ID verwendet. Wenn es keine solche ID gibt, dann wird der Klassenname auch als Topic-ID verwendet.

UML-Assoziation

Modelliert eine Topic Map-Beziehung, sofern kein Stereotyp etwas anderes angibt. Der Name der UML-Assoziation ist der Name der Topic Map-Beziehung. Die UML-Rollenbezeichner entsprechen den Topic Map-Rollenbezeichnern.

UML-Instanziierungsassoziation

Modelliert die instanceOf-Beziehung einer Topic Map.

Unausgeschmückte UML-Generalisierungsassoziation (Vererbung)

Wird als Abkürzung verwendet, um die kanonische Beziehung superclass-subclass anzugeben, deren Enden automatisch die Rollen superclass und subclass zugewiesen werden. Genau die gleiche Beziehung verbindet Klassen mit dem baseName-Stereotyp mit den Topic-Klassen, für die sie einen Namen mit einem alternativen Gültigkeitsbereich darstellen. Die Verallgemeinerung mit dem variant-Stereotyp gibt auch Topic-Varianten an.

 

Anmerkung:
Falls Sie Rational Rose benutzen und Sie damit Ihre Modelle nicht als XMI speichern können, dann sollten Sie eine Erweiterung namens XMI oder RoseXMI herunterladen. Ich habe Rose nur zum Testen dieses Beispiels benutzt, aber andere UML-Werkzeuge wie z.B. Together Control Center von TogetherSoft unterstützen XMI ebenfalls, und dieses Beispiel dürfte auch mit diesen Werkzeugen funktionieren.

Es folgt das Stylesheet, das XMI nach XTM transformiert. Es verwendet mehrere Entities, damit die langen XMI-Elementnamen übersichtlicher werden:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- XMI-Organisationsstrukturen auf oberer Ebene -->
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!ENTITY FC "Foundation.Core">
  <!ENTITY FX "Foundation.Extension_Mechanisms">
  <!ENTITY FD "Foundation.Data_Types">
  <!ENTITY MM "Model_Management.Model">
  <!ENTITY ME "&FC;.ModelElement">
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- Abkürzungen für Grundelemente von XMI-Dateien, die für dieses Stylesheet von besonderem Interesse sind. -->
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- Einige generische UML-Elemente -->
  <!ENTITY ELEM "&FC;.Namespace.ownedElement">
  <!-- Die Assoziation als Ganzes -->
  <!ENTITY ASSOC "&FC;.Association">
  <!-- Der Verbindungsteil der Assoziation -->
  <!ENTITY CONN "&ASSOC;.connection">
  <!-- Die Enden der Assoziation. -->
  <!ENTITY END "&ASSOC;End">
  <!ENTITY CONNEND "&CONN;/&END;">
  <!ENTITY ENDTYPE "&END;.type">
  <!-- Eine UML-Klasse -->
  <!ENTITY CLASS "&FC;.Class">
  <!-- Der Name eines UML-Entitys -->
  <!ENTITY NAME "&FC;.ModelElement.name">
  <!-- Ein UML-Sterotyp -->
  <!ENTITY STEREOTYPE "&ME;.stereotype/&FX;.Stereotype">
  <!-- Der Ort, an dem UML-Dokumentation in XMI gespeichert wird, die wir für Ressourcendaten benutzen -->
  <!ENTITY TAGGEDVALUE "&ME;.taggedValue/&FX;.TaggedValue/&FX;.TaggedValue.value">
  <!-- Eine Supertyp-Relation (Vererbung) -->
  <!ENTITY SUPERTYPE "&FC;.Generalization.supertype">
  <!ENTITY SUBTYPE "&FC;.Generalization.subtype">
  <!ENTITY SUPPLIER "&FC;.Dependency.supplier">
  <!ENTITY CLIENT "&FC;.Dependency.client">
  <!ENTITY DEPENDENCY "/XMI/XMI.content/&MM;/&ELEM;/&FC;.Dependency">
  <!ENTITY EXPRBODY "&FC;.Attribute.initialValue/&FD;.Expression/&FD;.Expression.body">
  <!ENTITY ATTR "&CLASS;ifier.feature/&FC;.Attribute">
  <!-- Wird benutzt, um auf Standard-XTM-PSIs zu zeigen -->
  <!ENTITY TM.ORG "http://www.topicmaps.org/xtm/index.html">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xtm="http://www.topicmaps.org/xtm/1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
  <xsl:param name="termOnErr" select="true( )"/>
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

Verwenden Sie Schlüssel, um die Identifikation von Stereotypen zu vereinfachen und um UML-Assoziationen zu traversieren, die Querverweise zwischen xmi.id- und xmi.idref-Attributen verwenden:

  <!-- Indexklassen nach ihren Namen -->
  <xsl:key name="classKey" match="&CLASS;" use="@xmi.id"/>
  <!-- Indexstereotypen nach Namen und xmi.id -->
  <xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="@xmi.id"/>
  <xsl:key name="stereotypeKey" match="&FX;.Stereotype" use="&NAME;"/>
  <!-- Die xmi.ids von Stereotypen haben bisher Topic Maps in UML kodiert. Wir benutzen sie hier als effizientes Mittel, um zu überprüfen, ob ein Sterotyp an ein Element angehängt ist. -->
  <xsl:variable name="OCCURRENCE_ID" select="key('stereotypeKey','occurrence')/@xmi.id"/>
  <xsl:variable name="RESOURCE_ID" select="key('stereotypeKey','resource')/@xmi.id"/>
  <xsl:variable name="TOPIC_ID" select="key('stereotypeKey','topic')/@xmi.id"/>
  <xsl:variable name="SUBJECT_ID" select="key('stereotypeKey','subject')/@xmi.id"/>
  <xsl:variable name="BASENAME_ID" select="key('stereotypeKey','baseName')/@xmi.id"/>
  <xsl:variable name="SCOPE_ID" select="key('stereotypeKey','scope')/@xmi.id"/>
  <xsl:variable name="VARIANT_ID" select="key('stereotypeKey','variant')/@xmi.id"/>

Sie können ein XMI-UML-Modell in zwei Durchgängen in eine Topic Map konvertieren. Zuerst importieren Sie die Topics aus den Klassen, und dann importieren Sie XTM-Beziehungen aus UML-Assoziationen:

  <xsl:template match="/">
    <xtm:topicMap>
      <xsl:apply-templates mode="topics"/>
      <xsl:apply-templates mode="associations"/>
    </xtm:topicMap>
  </xsl:template>

Die einzigen Klassen, die in Topics übersetzt werden sollten, sind jene ohne Stereotyp und jene mit einem topic-Stereotyp. Die anderen Klassen im Modell sind Darstellungen, auf die ein Topic verweisen kann, z.B. Beschreibungen eines Aussagegegenstandes und Ressourcen:

  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- Übersetzung von UML-Klassen nach TOPICS                           -->
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <xsl:template match="&ELEM;/&CLASS;" mode="topics">
    <!-- Topics werden als Klassen modelliert, deren Stereotyp entweder leer oder gleich 'topic' ist. -->
    <xsl:if test="not(&STEREOTYPE;/@xmi.idref) or &STEREOTYPE;/@xmi.idref = $TOPIC_ID">
      <xsl:variable name="topicId">
        <xsl:call-template name="getTopicId">
          <xsl:with-param name="class" select="."/>
          <xsl:with-param name="prefix" select="''"/>
        </xsl:call-template>
      </xsl:variable>
      <xtm:topic id="{$topicId}">
        <!-- Dieses for-each dient allein dazu, den Kontext zu dem optionalen Attribut Core.Attribute namens 'subjectIdentityid' zu wechseln. -->
        <xsl:for-each select="&ATTR;[&NAME; = 'subjectIdentity']">
          <xtm:subjectIdentity>
            <xtm:subjectIndicatorRef xlink:href="{&EXPRBODY;}"/>
          </xtm:subjectIdentity>
        </xsl:for-each>
        <xtm:baseName>
          <xtm:baseNameString>
            <xsl:value-of select="&NAME;"/>
          </xtm:baseNameString>
        </xtm:baseName>
        <xsl:apply-templates select="." mode="getAlternateBaseNames"/>
        <xsl:apply-templates select="." mode="getVariants"/>
        <xsl:apply-templates select="." mode="getInstanceOf">
          <xsl:with-param name="classId" select="@xmi.id"/>
        </xsl:apply-templates>
        <xsl:apply-templates select="." mode="getOccurrences"/>
      </xtm:topic>
    </xsl:if>
  </xsl:template>
  <!-- Gibt die Topic-ID einer Topic-Klasse zurück, die deren id-Attributwert oder Name ist. -->
  <xsl:template name="getTopicId">
    <xsl:param name="class"/>
    <xsl:param name="prefix" select="'#'"/>
    <xsl:for-each select="$class">
      <xsl:choose>
        <xsl:when test="&ATTR;/&NAME; = 'id' ">
          <xsl:value-of select="&ATTR;[&NAME; = 'id']/&EXPRBODY;"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat($prefix,&NAME;)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  <!-- Gibt die Aussagegegenstands-ID der Aussagegegenstandsklasse zurück, die aus ihrem subjectIdentity-Attributwert oder Namen besteht. -->
  <xsl:template name="getSubjectIdentity">
    <xsl:param name="class"/>
    <xsl:for-each select="$class">
      <xsl:choose>
        <xsl:when test="&ATTR;/&NAME; = 'subjectIdentity' ">
          <xsl:value-of select="&ATTR;[&NAME; = 'subjectIdentity']&EXPRBODY;"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat('#',&NAME;)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  <!-- Gibt die Ressourcen-ID einer Ressourcenklasse zurück, die aus ihrem resourceName-Attributwert oder Namen besteht. -->
  <xsl:template name="getResourceIdentity">
    <xsl:param name="class"/>
    <xsl:for-each select="$class">
      <xsl:choose>
        <xsl:when test="&ATTR;/&NAME; = 'resourceName' ">
          <xsl:value-of select="&ATTR;[&NAME; = 'resourceName']/&EXPRBODY;"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="concat('#',&NAME;)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

Alternative Benennungen und Varianten können Sie als Spezialisierung der Basis-Topic-Klasse mit Hilfe der UML-Generalisierungsassoziation modellieren. Je nach Sichtweise können Sie das als ganz natürlich oder als Missbrauch eines Konzepts betrachten. Auf jeden Fall funktioniert es und ermöglicht einen visuellen Hinweis im UML-Diagramm, mit dem Sie sich nicht ausschließlich auf Stereotyp-Tags verlassen müssen:

  <!-- Alternative Benennungen werden durch die Traversierung von UML-Generalisierungsrelationen und die Suche nach baseName-Stereotypen gefunden. -->
  <xsl:template match="&ELEM;/&CLASS;" mode="getAlternateBaseNames">
    <xsl:variable name="xmiId" select="@xmi.id"/>
    <xsl:for-each select="../&FC;.Generalization [&SUPERTYPE;/&CLASS;/@xmi.idref = $xmiId]">
      <xsl:variable name="subtypeXmiId" select="&FC;.Generalization.subtype/&CLASS;/@xmi.idref"/>
      <xsl:variable name="class" select="key('classKey',$subtypeXmiId)"/>
      <xsl:if test="$class/&STEREOTYPE;/@xmi.idref = $BASENAME_ID">
        <xsl:variable name="name" select="$class/&NAME;"/>
        <xtm:baseName>
          <xsl:call-template name="getScope">
            <xsl:with-param name="class" select="$class"/>
          </xsl:call-template>
          <xtm:baseNameString>
            <xsl:value-of select="substring-after($name,'::')"/>
          </xtm:baseNameString>
        </xtm:baseName>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
  <!-- Varianten werden durch die Traversierung von UML-Generalisierungsrelationen und die Suche nach baseName-Stereotypen gefunden. -->
  <xsl:template match="&ELEM;/&CLASS;" mode="getVariants">
    <xsl:variable name="xmiId" select="@xmi.id"/>
    <xsl:for-each select="../&FC;.Generalization [&SUPERTYPE;/&CLASS;/@xmi.idref = $xmiId]">
      <xsl:variable name="subtypeXmiId" select="&FC;.Generalization.subtype/&CLASS;/@xmi.idref"/>
      <xsl:variable name="variantClass" select="key('classKey',$subtypeXmiId)"/>
      <xsl:if test="$variantClass/&STEREOTYPE;/@xmi.idref = $VARIANT_ID">
        <xsl:variable name="name" select="$variantClass/&NAME;"/>
        <xtm:variant>
          <xtm:variantName>
            <xsl:call-template name="resourceRep">
              <xsl:with-param name="class" select="$variantClass"/>
            </xsl:call-template>
          </xtm:variantName>
          <xtm:parameters>
            <xsl:call-template name="getVariantParams">
              <xsl:with-param name="class" select="$variantClass"/>
            </xsl:call-template>
          </xtm:parameters>
          <!-- Wechselt Kontext zu dieser Variante, um zu verschachtelten Varianten zu kommen, falls vorhanden. -->
          <xsl:apply-templates select="$variantClass" mode="getVariants"/>
        </xtm:variant>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
  <!-- Holt die Parameter einer Variante aus den Attributen der Variantenklasse -->
  <xsl:template name="getVariantParams">
    <xsl:param name="class"/>
    <xsl:if test="not($class/&ATTR;)">
      <xsl:message terminate="{$termOnErr}">A variant must have at least one parameter.</xsl:message>
    </xsl:if>
    <xsl:for-each select="$class/&ATTR;">
      <!-- Ein Parameter wird entweder als subjectIndicatorRef oder als topicRef modelliert -->
      <xsl:choose>
        <xsl:when test="&STEREOTYPE;/@xmi.idref = $SUBJECT_ID">
          <xtm:subjectIndicatorRef xlink:href="{&EXPRBODY;}"/>
        </xsl:when>
        <xsl:otherwise>
          <xtm:topicRef xlink:href="{&EXPRBODY;}"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

Die Belegstellen in Topic Maps werden als Beziehungen zu Klassen modelliert, die Daten oder Verweise auf Ressourcen enthalten. Da die Daten von Inline-Ressourcen zu umfangreich sein können, um noch schön in einen Attributwert zu passen, kann die Attributbeschreibung in diesem Beispiel als alternativer Behälter von Ressourcendaten verwendet werden:

  <!-- Topic Map-Belegstellen werden als Assoziationen zu Klassen modelliert, die Ressourcenreferenzen oder -daten enthalten. -->
  <xsl:template match="&ELEM;/&CLASS;" mode="getOccurrences">
    <xsl:variable name="xmiId" select="@xmi.id"/>
    <!-- Suche in den Assoziationen, an denen diese Klasse beteiligt ist -->
    <xsl:for-each select="../&ASSOC;[&CONN;/*/&ENDTYPE;/&CLASS;/@xmi.idref = $xmiId]">
      <!-- Teste Vorhandensein des Stereotyps occurrence -->
      <xsl:if test="&STEREOTYPE;/@xmi.idref = $OCCURRENCE_ID">
        <!-- Hole die id der Ressource, indem das andere Ende der occurrence-Assoziation betrachtet wird. -->
        <xsl:variable name="resourceId" select="&CONN;/*/&ENDTYPE;/&CLASS;[@xmi.idref != $xmiId]/@xmi.idref"/>
        <!-- Hole die Klasse, die die Ressource darstellt -->
        <xsl:variable name="resourceClass" select="key('classKey',$resourceId)"/>
        <xtm:occurrence>
          <xsl:apply-templates select="." mode="getInstanceOf">
            <xsl:with-param name="classId" select="$resourceId"/>
          </xsl:apply-templates>
          <!-- TODO: Das kann noch nicht modelliert werden!
            <xsl:call-template name="getScope">
              <xsl:with-param name="class"/>
            </xsl:call-template>
          -->
          <!-- Wir können entweder eine Ressourcenreferenz oder Ressourcendaten haben. Falls die Klasse das Attribut resourceData hat, dann ist Letzteres der Fall.  -->
          <xsl:call-template name="resourceRep">
            <xsl:with-param name="class" select="$resourceClass"/>
          </xsl:call-template>
        </xtm:occurrence>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
  <!-- Dieses Template bestimmt, wie die Ressource dargestellt wird -->
  <xsl:template name="resourceRep">
    <xsl:param name="class" />
    <xsl:variable name="resourceData">
      <!-- for-each für einen Kontextwechsel -->
      <xsl:for-each select="$class/&ATTR;[&NAME; = 'resourceData']">
        <xsl:choose>
          <!-- Die Ressourcendaten wurden in der Dokumentation des UML-Attributs kodiert -->
          <xsl:when test="&TAGGEDVALUE;">
            <xsl:value-of select="&TAGGEDVALUE;"/>
          </xsl:when>
          <!-- Die Ressourcendaten wurden im UML-Attributwert kodiert -->
          <xsl:otherwise>
            <xsl:value-of select="&EXPRBODY;"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:variable>
    <!-- Wenn wir Ressourcendaten haben, benutzen wir sie, sonst nehmen wir an, der Benutzer meinte eine Referenz. -->
    <xsl:choose>
      <xsl:when test="string($resourceData)">
        <xtm:resourceData>
          <xsl:value-of select="$resourceData"/>
        </xtm:resourceData>
      </xsl:when>
      <xsl:otherwise>
        <xsl:variable name="resource">
          <xsl:call-template name="getResourceIdentity">
            <xsl:with-param name="class" select="$class"/>
          </xsl:call-template>
        </xsl:variable>
        <xtm:resourceRef xlink:href="{$resource}"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

Die instanceOf-Beziehungen in XTM werden als UML-Abhängigkeitsassoziationen modelliert, die auch Instanziierungen genannt werden. Diese Darstellung von instanceOf ist ziemlich nahe liegend:

  <!-- Dieses Template findet heraus, ob eine Topic-Klasse irgendwelche instanceOf-Beziehungen hat. -->
  <xsl:template match="&ELEM;/&CLASS;" mode="getInstanceOf">
    <xsl:param name="classId"/>
    <!-- Wir iterieren über die dependency-Relationen und bestimmen, wie die Instanz dargestellt wird. -->
    <xsl:for-each select="&DEPENDENCY;[&CLIENT;/&CLASS;/@xmi.idref = $classId]">
      <xtm:instanceOf>
        <xsl:variable name="instanceClass" select="key('classKey',&SUPPLIER;/&CLASS;/@xmi.idref)"/>
        <!-- Finde heraus, ob die Instanz als Aussagegegenstand oder als Topic modelliert ist. -->
        <xsl:variable name="sterotypeId" select="$instanceClass/&STEREOTYPE;/@xmi.idref"/>
        <xsl:choose>
          <!-- Das gilt bei der Beschreibung eines Aussagegegenstands -->
          <xsl:when test="$sterotypeId = $SUBJECT_ID">
            <xsl:variable name="subjectIdentity">
              <xsl:call-template name="getSubjectIdentity">
                <xsl:with-param name="class" select="$instanceClass"/>
              </xsl:call-template>
            </xsl:variable>
            <xsl:if test="not(normalize-space($subjectIdentity))">
              <xsl:message terminate="{$termOnErr}">Subject with no identity!</xsl:message>
            </xsl:if>
            <xtm:subjectIndicatorRef xlink:href="{$subjectIdentity}"/>
          </xsl:when>
          <!-- Sonst wird die Instanz durch ein Topic dargestellt -->
          <xsl:when test="not($sterotypeId) or $sterotypeId = $TOPIC_ID">
            <xsl:variable name="topicId">
              <xsl:call-template name="getTopicId">
                <xsl:with-param name="class" select="$instanceClass"/>
              </xsl:call-template>
            </xsl:variable>
            <xsl:if test="not(normalize-space($topicId))">
              <xsl:message terminate="{$termOnErr}">Topic with no id!</xsl:message>
            </xsl:if>
            <topicRef xlink:href="{$topicId}"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:message terminate="{$termOnErr}">
              <xsl:text>instanceOf must point to a topic or a subject. </xsl:text>
              <xsl:value-of select="$instanceClass/&NAME;"/>
              <xsl:text> is a </xsl:text>
              <xsl:value-of select="key('stereotypeKey',$sterotypeId)/&NAME;"/>
              <xsl:text>.&#xa;</xsl:text>
            </xsl:message>
          </xsl:otherwise>
        </xsl:choose>
      </xtm:instanceOf>
    </xsl:for-each>
  </xsl:template>
  <xsl:template name="getScope">
    <xsl:param name="class"/>
    <xsl:variable name="classesAssociations" select="/*/XMI.content/*/&ELEM;/&ASSOC;[&CONN;/*/&FC;.AssociationEnd.type/&CLASS;/@xmi.idref = $class/@xmi.id]"/>
    <xsl:variable name="scopeAssociations" select="$classesAssociations[&FC;.ModelElement.stereotype/&FX;.Stereotype/@xmi.idref = $SCOPE_ID]"/>
    <xsl:if test="$scopeAssociations">
      <xtm:scope>
        <xsl:for-each select="$scopeAssociations">
          <xsl:variable name="targetClassId" select="&CONN;/*/&ENDTYPE;/&CLASS;[@xmi.idref != $class/@xmi.id]/@xmi.idref"/>
          <xsl:variable name="targetClass" select="key('classKey',$targetClassId)"/>
          <xsl:call-template name="getScopeRef">
            <xsl:with-param name="class" select="$targetClass"/>
          </xsl:call-template>
        </xsl:for-each>
      </xtm:scope>
    </xsl:if>
  </xsl:template>
  <xsl:template name="getScopeRef">
    <xsl:param name="class"/>
    <xsl:variable name="stereotypeId" select="$class/&FC;.ModelElement.stereotype/&FX;.Stereotype/@xmi.idref"/>
    <xsl:choose>
      <xsl:when test="not($stereotypeId) or $stereotypeId = $TOPIC_ID">
        <xsl:variable name="topidId">
          <xsl:call-template name="getTopicId">
            <xsl:with-param name="class" select="$class"/>
          </xsl:call-template>
        </xsl:variable>
        <xtm:topicRef xlink:href="{$topidId}"/>
      </xsl:when>
      <xsl:when test="$stereotypeId = $SUBJECT_ID">
        <xsl:variable name="subjectId">
          <xsl:call-template name="getSubjectIdentity">
            <xsl:with-param name="class" select="$class"/>
          </xsl:call-template>
        </xsl:variable>
        <xtm:subjectIndicatorRef xlink:href="{$subjectId}"/>
      </xsl:when>
      <xsl:when test="$stereotypeId = $RESOURCE_ID">
        <xsl:variable name="resourceId">
          <xsl:call-template name="getResourceIdentity">
            <xsl:with-param name="class" select="$class"/>
          </xsl:call-template>
        </xsl:variable>
        <xtm:resourceRef xlink:href="{$resourceId}"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message terminate="{$termOnErr}">A Scope must be either a topicRef, subjectRef, or resourceRef!</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template match="text( )" mode="topics"/>
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- UML-ASSOZIATION NACH TOPIC-BEZIEHUNGEN                                  -->
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <xsl:template match="&ASSOC;" mode="associations">
    <!-- Nur benannte UML-Assoziationen sind Topic Map-Beziehungen -->
    <xsl:if test="normalize-space(&NAME;)">
      <xtm:association id="{&NAME;}">
        <xtm:instanceOf>
          <topicRef xlink:href="{key('stereotypeKey',&STEREOTYPE;/@xmi.idref)/&NAME;}"/>
        </xtm:instanceOf>
        <xsl:for-each select="&CONNEND;">
          <xtm:member>
            <xtm:roleSpec>
              <xtm:topicRef xlink:href="{&NAME;}"/>
            </xtm:roleSpec>
            <xsl:variable name="topicId">
              <xsl:call-template name="getTopicId">
                <xsl:with-param name="class" select="key('classKey',&ENDTYPE;/&CLASS;/@xmi.idref)"/>
              </xsl:call-template>
            </xsl:variable>
            <xtm:topicRef xlink:href="{$topicId}"/>
          </xtm:member>
        </xsl:for-each>
      </xtm:association>
    </xsl:if>
  </xsl:template>
  <xsl:template match="&ELEM;/&FC;.Generalization" mode="associations">
    <xsl:variable name="subClassId" select="&SUBTYPE;/&CLASS;/@xmi.idref"/>
    <xsl:variable name="subClass" select="key('classKey',$subClassId)"/>
    <xsl:variable name="superClassId" select="&SUPERTYPE;/&CLASS;/@xmi.idref"/>
    <xsl:variable name="superClass" select="key('classKey',$superClassId)"/>
    <!-- Wenn es eine Generalisierungsbeziehung zwischen zwei Topics gibt, verwenden wir diese als Hinweis auf eine kanonische Oberklasse-Unterklasse-Beziehung, Idealerweise würden wir das Fehlen eines Stereotyps bei der Generalisierung verwenden, aber meine XMI-Version speichert keine Stereotyp-Angaben über Generalisierungen. -->
    <xsl:if test="(not($subClass/&STEREOTYPE;/@xmi.idref) or $subClass/&STEREOTYPE;/@xmi.idref = $TOPIC_ID) and (not($superClass/&STEREOTYPE;/@xmi.idref) or $superClass/&STEREOTYPE;/@xmi.idref = $TOPIC_ID)">
      <xtm:association>
        <xsl:variable name="id">
          <xsl:choose>
            <xsl:when test="normalize-space(&NAME;)">
              <xsl:value-of select="&NAME;"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="@xmi.id"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:attribute name="id">
          <xsl:value-of select="$id"/>
        </xsl:attribute>
        <xtm:instanceOf>
          <subjectIndicatorRef xlink:href="&TM.ORG;#psi-superclass-subclass"/>
        </xtm:instanceOf>
        <xtm:member>
          <xtm:roleSpec>
            <xtm:subjectIndicatorRef xlink:href="&TM.ORG;#psi-superclass"/>
          </xtm:roleSpec>
          <xsl:variable name="superClassTopicId">
            <xsl:call-template name="getTopicId">
              <xsl:with-param name="class" select="$superClass"/>
            </xsl:call-template>
          </xsl:variable>
          <xtm:topicRef xlink:href="{$superClassTopicId}"/>
        </xtm:member>
        <xtm:member>
          <xtm:roleSpec>
            <xtm:subjectIndicatorRef xlink:href="&TM.ORG;#psi-subclass"/>
          </xtm:roleSpec>
          <xsl:variable name="subClassTopicId">
            <xsl:call-template name="getTopicId">
              <xsl:with-param name="class" select="$subClass"/>
            </xsl:call-template>
          </xsl:variable>
          <xtm:topicRef xlink:href="{$subClassTopicId}"/>
        </xtm:member>
      </xtm:association>
    </xsl:if>
  </xsl:template>
  <xsl:template match="text( )" mode="associations"/>
</xsl:stylesheet>

Die folgenden Templates sind Teil des zweiten Durchgangs, in dem UML-Assoziationen, die wegen spezieller Stereotypen noch nicht behandelt wurden, in Topic Map-Beziehungen konvertiert werden. Hierbei ist das Stereotyp einer Beziehung jenes topicRef, das bestimmt, welche Art von Beziehung modelliert wird. Dazu wurde der Stereotypeintrag vor allem deswegen missbraucht, weil es in UML keinen anderen natürlichen Ort für diese Information gibt. Beziehungen mit Gültigkeitsbereichen sind ein Problem, das ich einfach ignoriert habe. Unter allen anderen Gesichtspunkten stimmt eine UML-Assoziation sehr gut mit dem Konzept einer Topic Map-Beziehung überein:

  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <!-- UML-ASSOZIATION NACH TOPIC-BEZIEHUNG                              -->
  <!--=  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =-->
  <xsl:template match="&ASSOC;" mode="associations">
    <!-- Nur benannte UML-Assoziationen sind Topic Map-Beziehungen -->
    <xsl:if test="normalize-space(&NAME;)">
      <xtm:association id="{&NAME;}">
        <xtm:instanceOf>
          <topicRef xlink:href="{key('stereotypeKey',&STEREOTYPE;/@xmi.idref)/&NAME;}"/>
        </xtm:instanceOf>
        <xsl:for-each select="&CONNEND;">
          <xtm:member>
            <xtm:roleSpec>
              <xtm:topicRef xlink:href="{&NAME;}"/>
            </xtm:roleSpec>
            <xsl:variable name="topicId">
              <xsl:call-template name="getTopicId">
                <xsl:with-param name="class" select="key('classKey',&ENDTYPE;/&CLASS;/@xmi.idref)"/>
              </xsl:call-template>
            </xsl:variable>
            <xtm:topicRef xlink:href="{$topicId}"/>
          </xtm:member>
        </xsl:for-each>
      </xtm:association>
    </xsl:if>
  </xsl:template>
  <xsl:template match="&ELEM;/&FC;.Generalization" mode="associations">
    <xsl:variable name="subClassId" select="&SUBTYPE;/&CLASS;/@xmi.idref"/>
    <xsl:variable name="subClass" select="key('classKey',$subClassId)"/>
    <xsl:variable name="superClassId" select="&SUPERTYPE;/&CLASS;/@xmi.idref"/>
    <xsl:variable name="superClass" select="key('classKey',$superClassId)"/>

Wenn es eine Generalisierungsbeziehung zwischen zwei Topics gibt, sollten Sie sie als Hinweis auf eine kanonische superclass-subclass-Beziehung verwenden. Die XTM-Spezifikation unterstützt diese wichtige Beziehung explizit über sogenannte veröffentlichte Beschreibungen eines Aussagegegenstandes (engl. published subject indicators bzw. PSI). Idealerweise würden Sie dafür das Fehlen eines Stereotyps bei der Generalisierung ausnutzen, aber die XMI-Version, die ich verwende, speichert keine Stereotyp-Angaben bei Generalisierungen:

    <xsl:if test="(not($subClass/&STEREOTYPE;/@xmi.idref) or $subClass/&STEREOTYPE;/@xmi.idref = $TOPIC_ID) and (not($superClass/&STEREOTYPE;/@xmi.idref) or $superClass/&STEREOTYPE;/@xmi.idref = $TOPIC_ID)">
      <xtm:association>
        <xsl:variable name="id">
          <xsl:choose>
            <xsl:when test="normalize-space(&NAME;)">
              <xsl:value-of select="&NAME;"/>
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="@xmi.id"/>
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <xsl:attribute name="id">
          <xsl:value-of select="$id"/>
        </xsl:attribute>
        <xtm:instanceOf>
          <subjectIndicatorRef xlink:href="&TM.ORG;#psi-superclass-subclass"/>
        </xtm:instanceOf>
        <xtm:member>
          <xtm:roleSpec>
            <xtm:subjectIndicatorRef xlink:href="&TM.ORG;#psi-superclass"/>
          </xtm:roleSpec>
          <xsl:variable name="superClassTopicId">
            <xsl:call-template name="getTopicId">
              <xsl:with-param name="class" select="$superClass"/>
            </xsl:call-template>
          </xsl:variable>
          <xtm:topicRef xlink:href="{$superClassTopicId}"/>
        </xtm:member>
        <xtm:member>
          <xtm:roleSpec>
            <xtm:subjectIndicatorRef xlink:href="&TM.ORG;#psi-subclass"/>
          </xtm:roleSpec>
          <xsl:variable name="subClassTopicId">
            <xsl:call-template name="getTopicId">
              <xsl:with-param name="class" select="$subClass"/>
            </xsl:call-template>
          </xsl:variable>
          <xtm:topicRef xlink:href="{$subClassTopicId}"/>
        </xtm:member>
      </xtm:association>
    </xsl:if>
  </xsl:template>
  <xsl:template match="text( )" mode="associations"/>
</xsl:stylesheet>

Diskussion

Topic Maps repräsentieren Wissen über Gegenstände aus der realen Welt. Mit diesen Techniken können Computer und Menschen relevante Information schneller und mit höherer Genauigkeit finden. Über Topic Maps wurde 1993 erstmals diskutiert, als diese Ideen zuerst in einem Arbeitsdokument der Davenport-Gruppe beschrieben wurden. (Die Davenport-Gruppe wurde von einem Unix-Systemanbieter und weiteren Mitgliedern gegründet, darunter auch O'Reilly & Associates.) Das Paradigma wurde im Umfeld des GCA-Forschungsinstituts (das heute IDEAlliance heißt) im Zusammenhang mit Anwendungen von HyTime erweitert. Die XTM-Spezifikation war ein Produkt dieser Arbeit, die unter der Aufsicht einer unabhängigen Organisation namens TopicMaps.org koordiniert wurde.

Ein Topic ist ein elektronischer Stellvertreter eines Aussagegegenstands aus der realen Welt. Der Rockmusiker Ozzy Osborne ist ein solcher Gegenstand, aber weil Sie den echten Ozzy nicht in einem Computer speichern können, erzeugen Sie eben ein Ozzy-Topic als Ersatz. Topics haben einen Namen, der als Benennung (engl. base name) bezeichnet wird. Ein Topic kann einen universellen Namen haben (in der korrekten Terminologie als unbeschränkt bzw. engl. unconstrained bezeichnet) sowie mehrere andere Namen, die spezifisch innerhalb eines Gültigkeitsbereichs sind. Ein Gültigkeitsbereich (engl. scope) ist ein Kontext, in dem ein Topic Map-Merkmal gültig ist. Im Falle eines Topic-Namens kann ein Gültigkeitsbereich anzeigen, dass das Topic Ozzy Osborne im Gültigkeitsbereich namens legal identisch mit John Michael Osbourne ist. (Sofern John Michael seinen Namen nicht legal in Ozzy geändert hat, wobei in diesem Fall seine Mutter vielleicht die Einzige wäre, der dieser Gültigkeitsbereich etwas bedeuten würde.)

Ein Topic kann auf eine Belegstelle (engl. occurrence) als Ressource für relevante Informationen zu einem Topic verweisen. Eine Ressource kann auf einen adressierbaren Inhalt (resourceRef) oder auf den Inhalt selbst verweisen (resourceData).

Ein Topic kann Teil einer besonderen Beziehung namens instanceOf sein, die angibt, dass dieses Topic eine spezifische Instanz einer allgemeineren Klasse von Objekten ist. Die Klasse kann durch eine Referenz auf ein anderes Topic oder auf eine Beschreibung des Aussagegegenstandes festgelegt werden. Beschreibungen von Aussagegegenständen (engl. subject indicators) sind eine interessante Eigenschaft von Topic Maps. Sie machen es leichter, die Natur eines Gegenstandes anzugeben, indem sie ihn mit einer standardisierten veröffentlichten Adresse verknüpfen, wie sie z.B. von einem staatlichen Standardisierungsgremium geführt werden.

Topics können über Beziehungen miteinander verbunden sein. Eine Beziehung (engl. association) ist eine benannte Relation zwischen zwei oder mehreren Topics, bei der jedes Topic eine bestimmte Rolle in der Beziehung spielt.

Es gibt noch einige weitere interessante Eigenschaften von Topic Maps, die Wissen über Aussagegegenstände modellieren. Jene Leser, die sich für Topic Maps interessieren, sollten sich ruhig einmal die Spezifikation von XTM unter TopicMaps.org anschauen. Verglichen mit anderen Spezifikationen dieser Art ist diese auch für Leser mit wenig Vorwissen sehr gut zugänglich. Fast die gesamte XTM-Funktionalität wird auf irgendwelche Konstrukte in UML abgebildet (wie es im Lösungsabschnitt erklärt wurde). Bei dieser Abbildung ist der Umgang mit dem Begriff des Gültigkeitsbereichs in Topic Maps die Hauptschwierigkeit. Im Topic Map-Paradigma ist ein Gültigkeitsbereich eine Methode, die angibt, dass ein Topic-Merkmal nur in einem bestimmten Umfeld gültig ist. Gültigkeitsbereiche gelten für Benennungen, Beziehungen und Belegstellen. Diese Konventionen können zwar mit Gültigkeitsbereichen bei Benennungen umgehen, aber im Moment gilt das noch nicht für Beziehungen und Belegstellen. Das liegt daran, dass diese Konventionen als UML-Assoziationen modelliert werden, und in UML ist eine Assoziation normalerweise nicht kontextabhängig. Sie können diese Eigenschaft mit UML-Einschränkungen (engl. constraints) modellieren. Nur leider kann die in Rational Rose verfügbare XMI-Version mit Informationen zu Einschränkungen nichts anfangen. In der Praxis sind Gültigkeitsbereiche aber eine Topic Map-Funktion für Fortgeschrittene, die von vielen Benutzern nicht benötigt wird, d.h., dieses Manko dürfte besonders bei Topic Map-Anfängern kein wesentliches Problem sein.

Die folgende Abbildung ist ein Beispiel einer in UML dargestellten Topic Map. Dieses Topic modelliert Informationen über eine Tomate im Kontext einer Mahlzeit. Ich habe dieses Beispiel verwendet, obwohl es etwas skurril ist, weil Sam Hunting das gleiche Beispiel in dem Buch XML Topic Maps: Creating and Using Topic Maps for the Web (Addison-Wesley, 2002) verwendet hat. Darin hat er an diesem Beispiel viele Möglichkeiten von Topic Maps vorgeführt, mit denen Sie die Richtigkeit des resultierenden XMIs überprüfen können.

UML-Diagramm einer Tomaten-Topic Map

Abbildung: UML-Diagramm einer Tomaten-Topic Map.

Es folgt ein Teil der resultierenden XTM-Datei:

<xtm:topicMap xmlns:xtm="http://www.topicmaps.org/xtm/1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
  <xtm:topic id="EN">
    <xtm:subjectIdentity>
      <xtm:subjectIndicatorRef xlink:href="http://www.topicmaps.org/xtm/1.0/language.xtm#en"/>
    </xtm:subjectIdentity>
    <xtm:baseName>
      <xtm:baseNameString>EN</xtm:baseNameString>
    </xtm:baseName>
  </xtm:topic>
  <xtm:topic id="FR">
    <xtm:subjectIdentity>
      <xtm:subjectIndicatorRef xlink:href="http://www.topicmaps.org/xtm/1.0/language.xtm#fr"/>
    </xtm:subjectIdentity>
    <xtm:baseName>
      <xtm:baseNameString>FR</xtm:baseNameString>
    </xtm:baseName>
  </xtm:topic>
  <xtm:topic id="myTomato">
    <xtm:subjectIdentity>
      <xtm:subjectIndicatorRef xlink:href="http://www.fed.gov/usda/doc/tomato.htm#gradeA"/>
    </xtm:subjectIdentity>
    <xtm:baseName>
      <xtm:baseNameString>tomato</xtm:baseNameString>
    </xtm:baseName>
    <xtm:baseName>
      <xtm:scope>
        <xtm:topicRef xlink:href="#EN"/>
      </xtm:scope>
      <xtm:baseNameString>tomato</xtm:baseNameString>
    </xtm:baseName>
    <xtm:baseName>
      <xtm:scope>
        <xtm:topicRef xlink:href="#FR"/>
      </xtm:scope>
      <xtm:baseNameString>tomato</xtm:baseNameString>
    </xtm:baseName>
    <xtm:variant>
      <xtm:variantName>
        <xtm:resourceData>TMT</xtm:resourceData>
      </xtm:variantName>
      <xtm:parameters>
        <xtm:topicRef xlink:href="cell_phone"/>
        <xtm:topicRef xlink:href="TMT"/>
      </xtm:parameters>
    </xtm:variant>
    <xtm:occurrence>
      <xtm:resourceRef xlink:href="#tomato.gif"/>
    </xtm:occurrence>
  </xtm:topic>
  <xtm:topic id="myConfite">
    <xtm:baseName>
      <xtm:baseNameString>tomato confite farcie aux douze saveurs</xtm:baseNameString>
    </xtm:baseName>
    <xtm:instanceOf>
      <topicRef xlink:href="#desert"/>
    </xtm:instanceOf>
  </xtm:topic>
  <!-- Ausgelassen -->
  <xtm:association id="tomato_confite_association">
    <xtm:instanceOf>
      <topicRef xlink:href="ingredient_of"/>
    </xtm:instanceOf>
    <xtm:member>
      <xtm:roleSpec>
        <xtm:topicRef xlink:href="anIngredient"/>
      </xtm:roleSpec>
      <xtm:topicRef xlink:href="myTomato"/>
    </xtm:member>
    <xtm:member>
      <xtm:roleSpec>
        <xtm:topicRef xlink:href="aDish"/>
      </xtm:roleSpec>
      <xtm:topicRef xlink:href="myConfite"/>
    </xtm:member>
  </xtm:association>
  <xtm:association id="caramels_confite">
    <xtm:instanceOf>
      <topicRef xlink:href="ingredient_of"/>
    </xtm:instanceOf>
    <xtm:member>
      <xtm:roleSpec>
        <xtm:topicRef xlink:href="anIngredient"/>
      </xtm:roleSpec>
      <xtm:topicRef xlink:href="myCarmel"/>
    </xtm:member>
    <xtm:member>
      <xtm:roleSpec>
        <xtm:topicRef xlink:href="aDish"/>
      </xtm:roleSpec>
      <xtm:topicRef xlink:href="myConfite"/>
    </xtm:member>
  </xtm:association>
  <!-- Ausgelassen -->
</xtm:topicMap>

Siehe auch

UML und XMI sind Standards, die von der OMG (Object Management Group) definiert wurden. Weitere Informationen dazu finden Sie unter UML.org und CORBA®, XML And XMI® Resource Page.

TopicMaps.org ist die offizielle Site zu den Themen Topic Maps und XTM.

Das Rezept Websites aus XTM-Topic Maps generieren zeigt, wie aus Topic Maps Websites generiert werden.

  

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de