Switch-Code generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten den Switch-Code generieren, der empfangene Nachrichten an ihren Nachrichten-Handler leitet.

Lösung

Das Nachrichten-Repository speichert Information darüber, welche Prozesse welche Nachrichten erhalten. Daher können Sie aus einem Prozessnamen ein Nachrichten-Switch generieren, der eintreffende Nachrichten an ihre jeweiligen Handler leitet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text"/>
  <xsl:param name="process" select=" '*' "/>
  <xsl:variable name="message-dir" select=" 'messages' "/>
  <xsl:variable name="directory-sep" select=" '/' "/>
  <xsl:variable name="include-ext" select=" '.h' "/>
  <xsl:template match="MessageRepository">
    <!-- Generiere vorbereitende Maßnahmen in der Quelldatei -->
    <xsl:call-template name="file-start"/>
    <!-- Generiere Include-Anweisungen für die Nachrichten, die dieser Prozess empfängt. -->
    <xsl:apply-templates select="Messages/Message[Receivers/ProcessRef = $process or $process = '*']" mode="includes"/>
    <!-- Generiere vorbereitende Maßnahmen für den Nachrichten-Switch -->
    <xsl:call-template name="switch-start"/>
    <!-- Generiere Switch-Body -->
    <xsl:apply-templates select="Messages/Message[Receivers/ProcessRef = $process or $process = '*']" mode="switch"/>
    <!-- Generiere Switch-Ende -->
    <xsl:call-template name="switch-end"/>
    <!-- Generiere Dateiende -->
    <xsl:call-template name="file-end"/>
  </xsl:template>
  <!-- Generiere eine Include-Anweisung für jede Nachricht -->
  <xsl:template match="Message" mode="includes">
    <xsl:text>#include &lt;</xsl:text>
    <xsl:value-of select="$message-dir"/>
    <xsl:value-of select="$directory-sep"/>
    <xsl:value-of select="Name"/>
    <xsl:value-of select="$include-ext"/>
    <xsl:text>&gt;&#xa;</xsl:text>
  </xsl:template>
  <!-- Generiere Fallunterscheidung für alle Nachrichtentypen -->
  <xsl:template match="Message" mode="switch">case <xsl:value-of select="Name"/>_ID: <xsl:call-template name="case-action"/></xsl:template>
  <!-- Generiere die Aktion des Nachrichtenbehandlers -->
  <xsl:template name="case-action">return <xsl:value-of select="Name"/>(*static_cast&lt;const <xsl:value-of select="DataTypeName"/>*&gt;(msg.getData( ))).process( ) ;</xsl:template>
  <!-- Macht erst einmal nichts. Wird vom Benutzer überschrieben, wenn nötig. -->
  <xsl:template name="file-start"/>
  <xsl:template name="file-end"/>
  <!-- Generiere Anfang der switch-Anweisung -->
  <xsl:template name="switch-start">
    #include &lt;transport/Message.h&gt;
    #include &lt;transport/MESSAGE_IDS.h&gt;
    <xsl:text>&#xa;&#xa;</xsl:text>
    <xsl:call-template name="process-function"/>
    {
      switch (msg.getId( ))
      {
      </xsl:template>
      <xsl:template name="switch-end">
        return false ;
      }
    }
  </xsl:template>
  <!-- Generiere Signatur des nachrichtenverarbeitenden Einstiegspunkts -->
  <xsl:template name="process-function">bool processMessage(const Message&amp; msg)</xsl:template>
</xsl:stylesheet>

Dieses Beispiel produziert den folgenden Switch-Code, wenn Sie es auf Ihr Test-Repository anwenden:

#include <messages/ADD_STOCK_ORDER.h>
#include <messages/CANCEL_STOCK_ORDER.h>

#include <transport/Message.h>
#include <transport/MESSAGE_IDS.h>

bool processMessage(const Message& msg)

{
  switch (msg.getId( ))
  {

    case ADD_STOCK_ORDER_ID:

      return ADD_STOCK_ORDER(*static_cast<const
      AddStockOrderData*>(msg.getData( ))).process( ) ;

    case CANCEL_STOCK_ORDER_ID:

      return CANCEL_STOCK_ORDER(*static_cast<const
      CancelStockOrderData*>(msg.getData( ))).process( ) ;

    return false ;
  }
}

Diskussion

Nachrichtenverarbeitende Anwendungen verfügen immer über irgendeine Form von Nachrichten-Switching. Die Struktur dieser Switches kann ein wenig variieren, aber normalerweise müssen sie eine Bezeichner-ID der Nachricht überprüfen und diese dann an einen Handler weiterleiten. Der Bezeichner kann die Form eines Integers (wie in diesem Fall) oder eines Strings haben. In manchen Fällen gibt es mehrteilige Bezeichner aus einem Nachrichtentyp und -untertyp. Auf der Verarbeitungsseite kann der Handler die Form einer einfachen Funktion oder eines Objekts annehmen, das zur Verarbeitung der Nachricht instanziiert wird. Es ist wichtig, auch einfache Code-Generatoren modular zu gestalten, damit sie in mehreren Kontexten wiederverwendet werden können. In einer Firma, für die ich einmal gearbeitet habe, hatte eine Gruppe beschlossen, etwas sehr Spannendes auf Basis von Perl zu bauen, das C++-Code aus einem XSD-Schema erzeugen konnte. Der Nutzen war ausreichend groß, dass auch andere Gruppen das Ding benutzen wollten. Aber leider hatten die Entwickler nur an ihre eigenen Bedürfnisse gedacht, und ihre ansonsten beeindruckende Lösung konnte nicht wiederverwendet werden. Selbst die Entwicklung sehr einfacher Code-Generatoren ist eine erhebliche Aufgabe, und daher sollten Sie versuchen, sie so allgemein zu machen, wie es nur praktisch möglich ist, damit Sie anderen jene Schmerzen ersparen, die Sie selbst durchmachen mussten.

Die hier angegebene Lösung enthält einige Stellen, an denen man variieren kann, was am Anfang der generierten Datei, am Anfang bzw. Ende des Switch-Codes sowie am Ende der Datei erscheint. Dank dieser Flexibilität kann dieser Generator im Rezept Pretty Printer generieren wiederverwendet werden. Dennoch könnte man ihn noch weiter verbessern. Dieser Generator nimmt z.B. an, dass das Nachrichten-Switching über eine switch-Anweisung in C erfolgt. Manche Anwendungen benutzen aber vielleicht eine if-else-Variante, besonders, wenn es sich bei den Nachrichten-IDs um Strings handelt. Wieder andere benutzen vielleicht eine Lookup-Tabelle, die Nachrichten-IDs auf einen Funktions-Pointer abbildet.

Kann man einen einzigen Generator so allgemein gestalten, dass er all diese Möglichkeiten abdeckt? Vielleicht, aber das Ergebnis wird dann sehr wahrscheinlich extrem komplex sein. Ein besserer Ansatz wäre, Generatorkomponenten zu erzeugen, die in komplexeren Szenarien wiederverwendet werden könnten. Hier ist ein Beispiel dafür, wie ein allgemeiner Generator für eine switch-Anweisung in C aussehen könnte:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xslt [
  <!-- Wird für die Steuerung der Code-Einrückung benutzt -->
  <!ENTITY INDENT "    ">
  <!ENTITY INDENT2 "&INDENT;&INDENT;">
]>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template name="gen-C-Switch">
    <xsl:param name="variable"/>
    <xsl:param name="cases" select="/.."/>
    <xsl:param name="actions" select="/.."/>
    <xsl:param name="default"/>
    <xsl:param name="baseIndent" select="'&INDENT;'"/>
    <xsl:param name="genBreak" select="true( )"/>
    <xsl:value-of select="$baseIndent"/>
    <xsl:text>switch (</xsl:text>
    <xsl:value-of select="$variable"/>
    <xsl:text>)&#xa;</xsl:text>
    <xsl:value-of select="$baseIndent"/>
    <xsl:text>{&#xa;</xsl:text>
    <xsl:for-each select="$cases">
      <xsl:variable name="pos" select="position( )"/>
      <xsl:value-of select="$baseIndent"/>
      <xsl:text>&INDENT;case </xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>:&#xa;</xsl:text>
      <xsl:call-template name="gen-C-Switch-caseBody">
        <xsl:with-param name="case" select="."/>
        <xsl:with-param name="action" select="$actions[$pos]"/>
        <xsl:with-param name="baseIndent" select="concat('&INDENT2;',$baseIndent)"/>
      </xsl:call-template>
      <xsl:if test="$genBreak">
        <xsl:value-of select="$baseIndent"/>
        <xsl:text>&INDENT2;break;</xsl:text>
      </xsl:if>
      <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:if test="$default">
      <xsl:value-of select="$baseIndent"/>
      <xsl:text>&INDENT;default:</xsl:text>
      <xsl:text>:&#xa;</xsl:text>
      <xsl:call-template name="gen-C-Switch-default-caseBody">
        <xsl:with-param name="action" select="$default"/>
        <xsl:with-param name="baseIndent" select="concat('&INDENT2;',$baseIndent)"/>
      </xsl:call-template>
      <xsl:text>&#xa;</xsl:text>
    </xsl:if>
    <xsl:value-of select="$baseIndent"/>
    <xsl:text>}&#xa;</xsl:text>
  </xsl:template>
  <!-- Hiermit wird standardmäßig eine leere Anweisung erzeugt. Überschreiben Sie das, um Code für case zu generieren. -->
  <xsl:template name="gen-C-Switch-caseBody">
    <xsl:param name="case"/>
    <xsl:param name="action"/>
    <xsl:param name="baseIndent"/>
    <xsl:value-of select="$baseIndent"/>
    <xsl:text>;</xsl:text>
  </xsl:template>
  <!-- Hiermit wird der Generator für reguläre case-Coderümpfe aufgerufen. Überschreiben Sie das, um etwas Spezielles für den Default-Fall zu machen. -->
  <xsl:template name="gen-C-Switch-default-caseBody">
    <xsl:param name="action"/>
    <xsl:param name="baseIndent"/>
    <xsl:call-template name="gen-C-Switch-caseBody">
      <xsl:with-param name="action" select="$action"/>
      <xsl:with-param name="baseIndent" select="$baseIndent"/>
    </xsl:call-template>
  </xsl:template>
</xsl:stylesheet>

XSLT erweitern und einbetten demonstriert einige Techniken, mit denen solche Generatoren noch weiter verallgemeinert werden können.

  

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