Daten-Wrapper generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten Klassen generieren, die einen Wrapper mit typsicherer Schnittstelle um die Daten aller jeweiligen Nachrichten bilden.

Lösung

Die Lösung funktioniert in zwei Betriebsarten. Wenn ein Nachrichtenname als Parameter angegeben wird, generiert sie einen Wrapper nur für diese Nachrichtendaten. Ansonsten generiert sie Wrapper für alle Nachrichten:

<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:strip-space elements="*"/>
  <!-- Die Nachricht, für die Daten generiert werden, '*' für alle. -->
  <xsl:param name="message" select=" '*' "/>
  <!-- Das Zielverzeichnis des generierten Codes -->
  <xsl:param name="generationDir" select=" 'src/' "/>
  <!-- Die gewünschte Erweiterung der C++-Headerdateien -->
  <xsl:param name="headerExt" select=" '.h' "/>
  <!-- Die gewünschte Erweiterung der C++-Quelldateien -->
  <xsl:param name="sourceExt" select=" '.C' "/>
  <!-- Ein Schlüssel, um Datentypen über den Namen zu finden -->
  <xsl:key name="dataTypes" match="Structure" use="Name" />
  <xsl:key name="dataTypes" match="Primitive" use="Name" />
  <xsl:key name="dataTypes" match="Array" use="Name" />
  <xsl:key name="dataTypes" match="Enumeration" use="Name" />
  <!-- Das oberste Template bestimmt, welche Nachrichten verarbeitet werden. -->
  <xsl:template match="/">
    <xsl:choose>
      <xsl:when test="$message = '*'">
        <xsl:apply-templates select="*/Messages/*"/>
      </xsl:when>
      <xsl:when test="*/Messages/Message[Name=$message]">
        <xsl:apply-templates select="*/Messages/Message[Name=$message]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message terminate="yes">No such message name [<xsl:value-of select="$message"/>]</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <!-- Falls der Datentyp der Nachricht im Repository enthalten ist, dann generiere Header- und Quelldatei für einen Daten-Wrapper dafür. -->
  <xsl:template match="Message">
    <xsl:choose>
      <xsl:when test="key('dataTypes',DataTypeName)">
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="header"/>
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="source"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:message>Message name [<xsl:value-of select="Name"/>] uses data [<xsl:value-of select="DataTypeName"/>] that is not defined in the repository.</xsl:message>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <!-- Wir generieren nur dann Headerdateien, wenn der Nachrichten-Datentyp eine Struktur ist. Der einzig andere typische Nachrichten-Datentyp ist XML. Für XML-Inhalte generieren wir keine Wrapper. -->
  <xsl:template match="Structure" mode="header">
    <xsl:document href="{concat($generationDir,Name,$headerExt)}">
    #include &lt;primitives/primitives.h&gt;
    class <xsl:value-of select="Name"/>
    {
    public:<xsl:text>&#xa;&#xa;</xsl:text>
      <xsl:for-each select="Members/Member">
        <xsl:text>    </xsl:text>
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
        get_<xsl:value-of select="Name"/>( ) const ;<xsl:text/>
        <xsl:text>&#xa;</xsl:text>
      </xsl:for-each>
      <xsl:text>&#xa;</xsl:text>
      private:<xsl:text>&#xa;&#xa;</xsl:text>
        <xsl:for-each select="Members/Member">
          <xsl:text>    </xsl:text>
          <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="data"/>  m_
          <xsl:value-of select="Name"/> ;<xsl:text/>
          <xsl:text>&#xa;</xsl:text>
        </xsl:for-each>
    } ;
    </xsl:document>
  </xsl:template>
  <!-- Wir generieren nur dann Quelldateien, wenn der Nachrichten-Datentyp eine Struktur ist. Der einzig andere typische Nachrichten-Datentyp ist XML. Für XML-Inhalte generieren wir keine Wrapper. -->
  <xsl:template match="Structure" mode="source">
    <xsl:document href="{concat($generationDir,Name,$sourceExt)}">
    #include "<xsl:value-of select="Name"/><xsl:value-of select="$headerExt"/>"
      <xsl:text/>
      <xsl:for-each select="Members/Member">
        <xsl:apply-templates select="key('dataTypes',DataTypeName)" mode="returnType"/>
        <xsl:text>  </xsl:text>
        <xsl:value-of select="../../Name"/>::get_<xsl:value-of select="Name"/>( ) const
        <xsl:text>&#xa;</xsl:text>
        <xsl:text>{&#xa;</xsl:text>
        <xsl:text>    return m_</xsl:text><xsl:value-of select="Name"/>
        <xsl:text>;&#xa;</xsl:text>
        <xsl:text>}&#xa;&#xa;</xsl:text>
      </xsl:for-each>
    </xsl:document>
  </xsl:template>
  <!-- Wir nehmen an, dass Member, die ihrerseits Strukturen sind, als Referenz zurückgegeben werden. -->
  <xsl:template match="Structure" mode="returnType">const <xsl:value-of select="Name"/>&amp;<xsl:text/></xsl:template>
  <!-- Wir bilden Primitive, die durch native C++-Typen dargestellt werden können, auf diese nativen Typen ab. Sonst nehmen wir an, dass das Primitiv extern definiert ist. -->
  <xsl:template match="Primitive" mode="returnType">
    <xsl:choose>
      <xsl:when test="Name='Integer' ">int</xsl:when>
      <xsl:when test="Name='Real' ">double</xsl:when>
      <xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template match="*" mode="returnType">
    <xsl:value-of select="Name"/>
  </xsl:template>
  <xsl:template match="Primitive" mode="data">
    <xsl:choose>
      <xsl:when test="Name='Integer' ">int</xsl:when>
      <xsl:when test="Name='Real' ">double</xsl:when>
      <xsl:otherwise><xsl:value-of select="Name"/></xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template match="*" mode="data">
    <xsl:value-of select="Name"/>
  </xsl:template>
</xsl:stylesheet>

Dieser Generator produziert nur eine get-Schnittstelle, aber Sie können ihn leicht dahingehend erweitern, dass er auch set-Funktionen oder andere Arten von Funktionen erzeugt. Hier ist ein Beispiel für eine generierte Header-Datei:

#include <primitives/primitives.h>

class AddStockOrderData
{
public:

    StkSymbol get_symbol( ) const ;
    Shares get_quantity( ) const ;
    BuyOrSell get_side( ) const ;
    OrderType get_type( ) const ;
    double get_price( ) const ;


private:

    StkSymbol  m_symbol ;
    Shares  m_quantity ;
    BuyOrSell  m_side ;
    OrderType  m_type ;
    double  m_price ;

} ;

Und hier ist ein Beispiel für eine cpp-Datei:

#include "AddStockOrderData.h"

StkSymbol  AddStockOrderData::get_symbol( ) const
{
    return m_symbol;
}

Shares  AddStockOrderData::get_quantity( ) const
{
    return m_quantity;
}

BuyOrSell  AddStockOrderData::get_side( ) const
{
    return m_side;
}

OrderType  AddStockOrderData::get_type( ) const
{
    return m_type;
}

double  AddStockOrderData::get_price( ) const
{
    return m_price;
}

Diskussion

Dieser Abschnitt verwendet den Begriff Wrapper als Bezeichnung für eine Klasse, die eine objektorientierte Schnittstelle zu Daten bietet, die ansonsten einfach nur aus einem altbekannten C-struct bestehen. Ich habe einmal an einem Projekt gearbeitet, in dem alle Nachrichten-Wrapper von Hand kodiert wurden. Auch wenn diese Arbeit mühsam war, so hat sie sich im Ergebnis mehr als gelohnt. Stellen Sie sich eine Nachricht vor, die Preise, Anzahlen und Datumsangaben enthält. Mit einem Integer-Typ man man vermutlich leicht all diese höheren Typen darstellen. Aber dann könnten Sie leicht einen Fehler machen und zwei Angaben vertauschen, ohne dass der Compiler es bemerkt. Mit solchen Wrappern können Sie Ihre Nachrichtendaten in eine Hülle verpacken, die Low-Level-Darstellungen in klassenbasierte Primitive wie Price, Qty und Date umwandelt. Ein automatisch generierter Wrapper bietet den gleichen Vorteil mit weniger Aufwand.

Mit einem Nachrichten-Repository und einem XSLT-basierten Generator können Sie die Produktion von Wrappern automatisieren. In der Praxis enthalten Wrapper manchmal einiges an zusätzlicher Intelligenz, und dann müssen Sie vielleicht weitere Metadaten im Repository speichern, um diese Intelligenz auch bei der entsprechenden Code-Generierung zu nutzen. Ein häufiger Fall tritt ein, wenn in den Nachrichtendaten Arrays vorkommen. Dann gibt es oft ein weiteres Feld, das angibt, wie viele Elemente in diesem Array gespeichert sind. Wenn Sie eine Wrapper-Funktion von Hand kodieren würden, um ein Element zu diesem Array hinzuzufügen, müssten Sie sich dieses Feld anschauen, um die nächsten freien Plätze herauszufinden, und es erhöhen, nachdem die neuen Daten hinzugefügt worden sind. Solchen Code können Sie nur dann generieren, wenn das Repository die Verbindung zwischen dem Feld mit der Array-Länge und dem des Arrays selbst kennt.

  

<< 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