Back To Top

XSLT: HTML-Formulare erzeugen


HTML-Formulare erzeugen

Comelio-Blog XSLT HTML-FormulareIm Rahmen einer Beratung stellte sich einmal das Problem, dass möglichst schnell eine Technik benötigt wurde, bei der automatisch viele verschiedene HTML-Formulare zu erzeugen waren. Nun gibt es immer verschiedene Möglichkeiten, HTML-Formulare mit vorgefertigten Klassen zu erzeugen. In PHP gibt es dafür nicht in der Sprache eingebundene Klassen, sodass man entweder auf Klassen zurückgreifen muss, die in diversen Foren angeboten werden, oder man entwickelt etwas Neues. Da wir – wie gerade schon erwähnt – nicht sehr begeistert von der Vorstellung sind, das Rad neu zu erfinden, haben wir uns auch für diese Frage einmal entspannt zurückgelehnt und kamen auf eine Idee, die wir bisher noch in keinen anderen Projekten außer unseren verwirklicht gesehen haben: der Einsatz von XML Schema.

Formular aus XML Schema

Der Einsatz von XML Schema beschränkt sich normalerweise auf den üblichen Einsatz, den es bei seiner Konzeption durch das W3C erhalten hat: der Datenmodellierung für XML-Dokumente. Allerdings lässt sich XML Schema auch für speziellere Einsatzbereiche wie bei der Modellierung von Datenbanken benutzen. Hier lässt sich beispielsweise über XSLT leicht (siehe Abschnitt SQL) der benötigte SQL-Quelltext für diese und jene Datenbank erzeugen.

Dann lassen sich allerdings auch viele weitere Einsatzbereiche denken, von denen wir vielleicht die meisten noch gar nicht umgepflügt haben, weil sich die Gelegenheit noch nicht bot und man andere Techniken verwendet hat. Sie lassen sich daran erkennen, dass Bezeichner für Datenstrukturen benötigt werden, wobei zusätzlich auch einige Eigenschaften zu den Datentypen, Wertebereichen und Einschränkungen der zulässigen Daten auf unterschiedliche Art und Weise erforderlich sind. Da in XML Schema immer auch fremde Attribute verwendet werden können, sind weitere Einsatzbereiche leicht denkbar, weil die benötigten zusätzlichen Attribute leicht in die bestehende Syntax eingebracht werden können.

Damit der modifizierte Einsatz auch für die Transformation mit XSLT möglichst leicht gelingt, lohnt es sich – wie oft im XML-Bereich – aus bestehenden Strukturen eine Untermenge auszuwählen und nur diese als für ein Gebiet/Projekt zulässig zu deklarieren. So erleichtert man sich die Verwendung, die Lokalisierung und die Transformation, da nicht jede syntaktische Spitzfindigkeit, die theoretisch zulässig ist, auch tatsächlich umgesetzt wird. Eine solche Umsetzung würde nämlich nur bedeuten, dass man auf sie – gerade bei alternativen Verfahren – entsprechend auch bei der Verarbeitung der XML-Schema-Dokumente eingehen muss. Damit hier keine Fehler oder Auslassungen auftreten, die möglicherweise später zu Fehlern oder Funktionsstörungen beim Betrieb der Lösung führen, lohnt es sich, die Syntax auf das wirkliche Notwendige zu reduzieren.

Einen weiteren Einsatzbereich, den wir jetzt nach dieser Vorrede präsentieren wollen, ist die Modellierung von Daten für Webformulare. Dazu eignet sich sowohl die attributorientierte wie auch die elementorientierte Form von einfachen XML-Schema-Dokumenten, in denen die zu verarbeitenden Elemente als Kindelemente von element-Elementen der obersten Ebene erscheinen. Als weitere Bedingung gilt, dass die Dokumentreihenfolge auch die Reihenfolge der Elemente im HTML-Formular wird. Wenn man möchte, könnte man noch ein weiteres Attribut einführen, das die Reihenfolge oder sogar Position bestimmt. Die Datentypen werden einfach übernommen, wobei allerdings in der hier vorgestellten einfachen Variante keine globalen einfachen und globalen komplexen Datentypen zulässig sind. Dies wäre durchaus denkbar, wenn bestimmte Strukturen wiederholt auftreten und man sie lieber auslagern wollte, doch muss man darauf mit einer eigenen Vorlage reagieren. Diese könnte beispielsweise den Namen des Datentyps abfragen und seine Eigenschaften mit einem passenden XPath-Ausdruck lokalisieren und dann wieder mit der Vorlage verarbeiten, die wir auch im nachfolgenden XSLT-Beispiel verwenden.

Die gerade gemachten kurzen Ausführungen sollen Ihnen zeigen, dass man natürlich stets solche Vorschläge verbessern, erweitern und damit komplexer gestalten kann. Dies ist eine Grundtatsache, die vermutlich in wirklich allen Lebensbereichen gilt. Allerdings sollten Sie sich auch die Einfachheit der hier vorgestellten Lösung ansehen und mit den Formularen vergleichen, die man normalerweise erstellt. Die einzige tatsächlich notwendige Verbesserung würde darin liegen, noch genauere Informationen zu speichern, welche Formularelemente zu verwenden sind. Alles andere ist denkbar, würde allerdings vielleicht in vielen Fällen den Rahmen sprengen und die Arbeit zu kompliziert gestalten. Sollten allerdings gerade bei den Formularen besonders viele Bedingungen gelten und sollten die Formulare für eine Anwendung besonders wichtig sein, dann könnte man sich wohlüberlegte Erweiterungen ausdenken.

Das nachfolgende Dokument enthält zunächst in der XML-Schema-Syntax Eigenschaftsvorgaben für ein zu erzeugendes Formular. Die Datentypen sind angegeben, wobei alle eine Längenbeschränkung und einige einen festen Wertebereich aufweisen. Die Längenschränkung findet man entweder in einem maxLength- oder in einem totalDigits-Element. Die Werte von festen Wertebereichen sind in enumeration-Elementen untergebracht.

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="812_01.xslt"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="MITARBEITER">
    <xs:complexType>
      <xs:sequence>
...
        <xs:element name="M_ANREDE">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="Herr"/>
              <xs:enumeration value="Frau"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="M_VORNAME">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:maxLength value="20"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
        <xs:element name="M_NACHNAME">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:maxLength value="30"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
...
        <xs:element name="M_FUNKTION">
          <xs:simpleType>
            <xs:restriction base="xs:string">
              <xs:enumeration value="Technik"/>
              <xs:enumeration value="Geschäftsführung"/>
              <xs:enumeration value="Kundenbetreuung"/>
            </xs:restriction>
          </xs:simpleType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

XML Schema für Formularerzeugung

Für die Arbeit mit XML Schema, um HTML-Formulare zu erzeugen, lassen sich verschiedene globale Parameter denken. Neben Aspekten des Layouts, die hier ausdrücklich nicht betrachtet werden sollen, sind es vor allen Dingen solche, die die direkte Funktionalität des Formulars betreffen. In unserem Fall greifen wie hier wiederum die beiden wichtigsten Einstellungen heraus: die Werte des action- und des method-Attributs für die Angabe der Seite, die nach dem Formularversand aufgerufen werden soll, und für die Angabe der Art und Weise, wie die Daten verschickt werden sollen.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output method="html" version="1.0"
       encoding="ISO-8859-1"/>
  <xsl:param name="action" select="'erfassung.php'"/>
  <xsl:param name="method" select="'post'"/>

Parameter

In der Startvorlage erzeugen wir nur das Grundgerüst der entstehenden HTML-Seite und wählen die Elemente auf der obersten Ebene aus. Damit ist es auch möglich, mehrere Formulare auf einer Seite zu erzeugen, wenn man denn mehrere solcher Elemente in der XML-Schema-Datei gespeichert hat. Für den Seitentitel verwenden wir jetzt das erste Element. Man könnte sich auch ein eigenes Attribut ausdenken oder einen allgemeinen Seitentitel verwenden.

  <!-- Startvorlage -->
  <xsl:template match="/xs:schema">
    <html>
      <head>
        <title>Erfassung <xsl:value-of select=
               "/xs:schema/xs:element/@name"/>
        </title>
      </head>
      <body>
        <xsl:apply-templates
             select="xs:element[parent::xs:schema]"/>
      </body>
    </html>
  </xsl:template>

Grundvorlage

Für die Kinder der Elemente der obersten Ebene erzeugen wir innerhalb eines form-Tags zwei verschiedene HTML-Formularelemente. Wir beschränken uns hier auf Eingabefelder (input-Elemente mit dem Wert text für das type-Attribut) und Auswahllisten. Letztere erscheinen immer dann, wenn der Wertebereich über enumeration-Elemente vorgegeben ist und daher fest vorliegt. Beide rufen nach einer Überprüfung, zu welcher Gruppe von Formularelementen sie gehören, eine entsprechende benannte Vorlage auf.

  <!-- Vorlage für Elemente der obersten Ebene -->
  <xsl:template match="xs:element[parent::xs:schema]">
    <h1>
      <xsl:value-of select="@name"/>
    </h1>
    <form>
      <xsl:attribute name="action"><xsl:value-of select="$action"/></xsl:attribute>
      <xsl:attribute name="method"><xsl:value-of select="$method"/></xsl:attribute>
      <xsl:attribute name="name"><xsl:value-of select="@name"/></xsl:attribute>
      <table>
        <xsl:variable name="aktuellesElement" select="@name"/>
        <xsl:for-each select="//xs:element
        [ancestor::xs:element/@name=$aktuellesElement]">
          <tr>
            <th>
              <xsl:value-of select="@name"/>
            </th>
            <td>
              <xsl:choose>
                <xsl:when test="count(xs:simpleType
                 /xs:restriction/xs:enumeration) >=1">
                  <xsl:call-template name="SelectListe"/>
                  <xsl:apply-templates select="@type | 
                   xs:simpleType/xs:restriction/@base"/>
                </xsl:when>
                <xsl:otherwise>
                  <xsl:call-template name="InputFeld"/>
                  <xsl:apply-templates select="@type | 
                   xs:simpleType/xs:restriction/@base"/>
                </xsl:otherwise>
              </xsl:choose>
            </td>
          </tr>
        </xsl:for-each>
        <tr>
          <th> </th>
          <td>
            <input type="submit" value="Senden" 
                   name="Senden"/>
            <input type="reset" value="Löschen"/>
          </td>
        </tr>
      </table>
    </form>
  </xsl:template>

Verarbeitung der Elemente

Für die Auswahlliste benötigen wir in HTML mehrere option-Elemente innerhalb des select-Elements. Die Werte der Optionen ermitteln sich direkt aus dem Wert des enumeration-Elements in XML Schema. Theoretisch möglich ist auch, dass der Wert in der für den Benutzer sichtbaren Browserausgabe ein anderer ist als der übertragene, der im value-Attribut des option-Elements steht. Möchte man diese Fähigkeit von HTML, die durchaus in vielen Fällen nützlich ist, auch in XML Schema umsetzen, benötigt man ein weiteres Attribut, das entweder die angezeigten oder im HTML-Quelltext im value-Attribut aufgeführten Werte enthalten kann.

Für das Eingabefeld mit einer Textzeile verhält es sich naturgemäß etwas anders. Da hier lediglich die Datentyplänge von Interesse ist, benötigen wir nur für das size-Attribut die entsprechenden Werte aus totalDigits oder maxLength. Möchte man weitere Werte im type-Attribut verwenden wie z.B. password, hidden, radio oder checkbox (wobei die beiden letzteren besondere Elemente darstellen), benötigt man in XML Schema auch ein eigenes Attribut.

Für die Datentypzuordnung, die die Daten aus dem Formular und aus der XML-Schema-Datei zu Datentypen in der Datenbank zuordnet, verwenden wir eine eigene Vorlage, die die passenden Werte ausliest und dann eine spezielle Zuordnungsvorlage aufruft. Da diese Vorlage ausgelagert ist und letztlich nur die passenden Datentypen für die Datenbank (in unserem Fall Oracle) bestimmt, lässt sich auch leicht eine andere Vorlage einfügen, die Datentypen von MS SQL Server oder MySQL erkennt. Alternativ könnten dies auch globale Parameter sein, die die Datentypen mit einer allgemeinen Bezeichnung beschreiben und dann konkrete Wertvorgaben für ein bestimmtes Datenbanksystem erwarten. Als zweite Alternative bietet sich an, den Datenbanknamen als globalen Parameter zu übergeben, um dann innerhalb der Datentypzuordnungsvorlage passend für diesen Wert aus einer umfangreichen Fallunterscheidung die geeigneten Werte auszuwählen.

Weil ein HTML-Formular keine Datentypen erkennt oder gar standardmäßig validieren kann, benötigen wir dafür ein geeignetes Validierungsprogramm in JavaScript (clientseitig) oder einer serverseitigen Programmiersprache wie PHP, Java etc. Wir erleichtern die Arbeit auf der anderen Seite erheblich, wenn wir allgemeine Validierungsregeln verwenden können, die wenigstens oder in einem ersten Schritt den Datentyp einfach analysieren können. Dazu verwenden wir für jedes Formularelement ein verstecktes Feld, in dem ganz einfach die Datentypinformation gespeichert ist. Im name-Attribut speichern wir den Namen des beschriebenen Formularelements, ergänzt um die Zeichenkette _TYP, im value-Attribut den ermittelten Datentypnamen.

Die Längenbeschränkung ist von allen gerade ausgeführten Aktivitäten in der Transformation die einfachste. Sie ermittelt den Wert des value-Attributs von maxLength.

Als Ergebnis erhalten wir ein einfaches, aber mit CSS sicherlich aufregend zu formatierendes HTML-Formular. Das entsprechende XSLT-Dokument lässt sich für viele verschiedene Situationen einsetzen und erzeugt auch bei wechselnden oder dynamisch zusammengesetzten XML-Schema-Dokumenten passende Formulare.