Relaxer & RelaxNGCC

zwei Java-Tools für RELAX-Schemata im Vergleich

XML Seminarvortrag
von
Markus Loose




Gliederung


Relaxer

Funktionalität

Relaxer ist ein von Asami Tomoharu in Java geschriebener Schema-Compiler für Schemata in RELAX Core und RELAX NG (RELAX-Schemata). Basierend auf RELAX-Schemata oder XML-Instanzen generiert Relaxer mit dem Document Object Model Java-Klassen, die zur Validierung und/oder Weiterverarbeitung in eigenen Programmen benutzt werden können.

Weiterhin kann Relaxer DTDs, RELAX Core-Schemata, RELAX NG-Schemata, XML-Schemata, XSLT-Stylesheets sowie XHTML-Formulare aufgrund von XML-Instanzen erstellen. Es können auch die beiden RELAX-Schemata (Core/NG) in DTDs, XML-Schemata oder in das jeweils andere RELAX-Schemata umgewandelt werden. Die generierten Java-Klassen können eine Vielzahl von Funktionalitäten unterstützen: SAX, JAXP (Java API for XML Processing), Java Database Connectivity (JDBC), JavaBeans, Remote Method Invocation (RMI), Java API for XML Messaging (JAXM) und Design Patterns.

Damit Relaxer benutzt werden kann, wird Java 2 und JAXP benötigt.

Optionen

Die beiden folgenden Tabellen geben einen Überblick über die möglichen Eingabeformate sowie über die verschiedenen Arten der Ausgabe. Darüber hinaus gibt es noch weitere allgemeine und generator-spezifische Optionen. Auf die fett gedruckten Optionen wird im nächsten Abschnitt genauer eingegangen.

Eingabe (Importer):

Dateiendung

Typ

.rxm

RELAX Core

.rlx

RELAX Core

.rxg

RELAX Namespace

.rng

RELAX NG

.xml

XML-Instanz

.rjdbc

RDBMS-Schema mittels JDBC

Ergebnis (Generator):

Option

Generator-Funktion

Erklärung

version

Versions-Information

 

help

Anzeigen der Hilfe

 

java

Java-Klassen

default-Option

jdbc

Java-Klassen für den Zugriff auf JDBC

Resultat: Java Data Access Objects (DAO)

cdl

Komponenten

JavaBeans, RMI

rng

RELAX NG

 

rxm

RELAX Core

 

dtd

DTD

 

xsd

XML-Schema

 

xslt

XSLT-Stylesheet

 

html

(X)HTML-Formular

 

editor

Daten für XML-Editor (XML-Vokabular)

 

meta

Relaxer-Modell (XML-Vokabular)

Meta-Informationen

setup

kopiert Spezifikationsdateien

relaxCore.dtd, datatypes.dtd, ...

Beispiele

Die Beispiele, die die grundlegenden Eigenschaften von Relaxer illustrieren sollen, orientieren sich an einer sehr stark vereinfachten Datenerfassung von Musik-Alben, wie z.B. in album1.xml:

<?xml version="1.0" encoding="UTF-8"?>
<album id="RHCP-2">
<band>Red Hot Chili Peppers</band>
<titel>Californication</titel>
<jahr>1999</jahr>
</album>

Schema-Erzeugung

Mit der Option -dtd läßt sich aus der Struktur einer XML-Instanz eine DTD erzeugen, wobei Relaxer das Inhaltsmodell nach strengen Annahmen produziert. Es wird z.B. angenommen, dass alle Elemente genau einmal vorkommen müssen und dass das id-Attribut notwendig ist. Durch den Aufruf von

relaxer -dtd album1.xml

wird album1.dtd erzeugt:

<!-- Generated by Relaxer 1.0RC -->
<!-- Wed May 07 12:11:43 GMT+01:00 2003 -->
 
<!ELEMENT album (band, titel, jahr)>
<!ATTLIST album id CDATA #REQUIRED>
 
<!ELEMENT titel (#PCDATA)>
 
<!ELEMENT band (#PCDATA)>
 
<!ELEMENT jahr (#PCDATA)>

Durch Hinzufügen weiterer XML-Instanzen können genauere Vorgaben an das Inhaltsmodell Relaxer mitgeteilt werden. So enthält album2.xml zwar kein id-Attribut, aber ein neues Element cds!

<?xml version="1.0" encoding="UTF-8"?>
<album>
<band>The Smashing Pumpkins</band>
<titel>Mellon Collie and the Infinite Sadness</titel>
<cds>2</cds>
<jahr>1995</jahr>
</album>

In einem Inhaltsmodell, welches für beide XML-Instanzen validierend ist, kann das id-Attribut nicht mehr vom Typ #REQUIRED sein; außerdem muss das optionale Element cds berücksichtigt werden. Die zutreffende DTD album2.dtd entsteht durch:

relaxer -dtd album2.xml album1.xml

<!-- Generated by Relaxer 1.0RC -->
<!-- Wed May 07 13:34:25 GMT+01:00 2003 -->

<!ELEMENT album (band, titel, cds?, jahr)>
<!ATTLIST album id CDATA #IMPLIED>

<!ELEMENT titel (#PCDATA)>

<!ELEMENT band (#PCDATA)>

<!ELEMENT cds (#PCDATA)>

<!ELEMENT jahr (#PCDATA)>

Genauso wird ein RELAX NG-Schema (album1.rng) aus diesen Instanzen generiert. Dabei benutzt Relaxer die Datentypen von XML-Schema (token, int), wodurch genauere Vorgaben realisiert werden können!

relaxer -rng album1.xml album2.xml

<?xml version="1.0" encoding="UTF-8" ?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0" 
         datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
  <start>
    <ref name="album"/>
  </start>
  <define name="album">
    <element name="album">
      <optional>
        <attribute name="id">
          <data type="token"/>
        </attribute>
      </optional>
      <element name="band">
        <data type="token"/>
      </element>
      <element name="titel">
        <data type="token"/>
      </element>
      <optional>
        <element name="cds">
          <data type="int"/>
        </element>
      </optional>
      <element name="jahr">
        <data type="int"/>
      </element>
    </element>
  </define>
</grammar>

Das Erstellen von XML-Schemata kann direkt erreicht werden:

relaxer -xsd album1.xml album2.xml

Oder über Umwandlung eines bestehenden RELAX-Schemas:

relaxer -xsd album1.rng

Alle produzierten Schemata sind oft nicht perfekt, aber stellen eine Erleichterung für manuelles Nachbessern dar, da sie Schreibarbeit sparen!

Erzeugung von XSLT-Stylesheets

Ein von Relaxer erzeugtes XSLT-Stylesheet (album1.xsl) realisiert eine identische Transformation der Eingabe(n). Solche Vorlage zum Überarbeiten erhält man durch:

relaxer -xslt album1.xml album2.xml

<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" method="xml"/>
  <xsl:template match="jahr">
    <jahr>
      <xsl:apply-templates/>
    </jahr>
  </xsl:template>
  <xsl:template match="band">
    <band>
      <xsl:apply-templates/>
    </band>
  </xsl:template>
  <xsl:template match="titel">
    <titel>
      <xsl:apply-templates/>
    </titel>
  </xsl:template>
  <xsl:template match="cds">
    <cds>
      <xsl:apply-templates/>
    </cds>
  </xsl:template>
  <xsl:template match="album[band and titel and jahr]">
    <album>
      <xsl:attribute name="id">
        <xsl:value-of select="@id"/>
      </xsl:attribute>
      <xsl:apply-templates/>
    </album>
  </xsl:template>
</xsl:stylesheet>

XHTML-Formulare

Relaxer ist in der Lage ein Formular zu erzeugen, dessen Eingabefelder den XML-Daten entsprechen; album.html ensteht durch folgende Kommandozeile:

relaxer -html album2.xml

Erstellen von Java-Klassen

Um Java-Quelltext vom RELAX-Schema album1.rng zu erzeugen, wird die Option -java verwendet:

relaxer -java album1.rng

Relaxer stellt folgende 4 Klassen (mit Javadoc-Kommentaren) her. Album.java basiert auf dem Datenmodell von album1.rng. Weiterhin entstehen RStack.java, UJAXP.java, URelaxer.java, die Album.java in der Funktionsweise unterstützen, aber nicht für den Benutzer zugänglich sind. Deshalb genügt es, einen Auszug von Album.java zu betrachten:

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
//import ...

public class Album
{
    // Default-Konstruktor
    public Album() {
        band_ = "";
        titel_ = "";
    }
    
    // Konstruktor akzeptiert Datei
    public Album(File file) 
    throws IOException, SAXException, ParserConfigurationException {
        setup(file);
    }    

    // Konstruktor akzeptiert eine SAX InputSource als Argument 
    public Album(InputSource is)
    throws IOException, SAXException, ParserConfigurationException {
        setup(is);
    }
    
    // weitere Konstruktoren

    
    // pro Element und pro Attribut gibt es jeweils eine set... und get... Methode
    public final String getId() {
        return (id_);
    }

    public final void setId(String id) {
        this.id_ = id;
    }
    
    // ...
    
    // stellt DOM-Präsentation des Album-Objektes her
    public Document makeDocument() throws ParserConfigurationException {
        Document doc = UJAXP.makeDocument();
        makeElement(doc);
        return (doc);
    }
    
    // stellt Text-Präsentation (in XML) des Album-Objektes her
    public String makeTextDocument() {
        StringBuffer buffer = new StringBuffer();
        makeTextElement(buffer);
        return (new String(buffer));
    }
    
    // ...
}

Nach der Compilation von Album.java und dem Schreiben einer eigener Klasse, z.B. ChangeAlbum.java, kann der erzeugte Code benutzt werden.

import java.io.File;
import java.io.IOException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;

public class ChangeAlbum {

   public static void main(String[] args)
	throws IOException, SAXException, ParserConfigurationException {

      if (args.length == 0) {
         System.out.println("Usage: java ChangeAlbum file.xml");
         System.exit(1);
      }

      // instanziere ein Album-Objekt mit einer Datei
      Album album = new Album(new File(args[0]));
 
      // Ausgabe der XML-Repräsentation des Dokuments
      System.out.println("\nOriginal document:\n");
      System.out.println(album.makeTextDocument());
 
      // ändere Inhalt   
      album.setId("DS-1");
      album.setBand("Die Schnitter");
      album.setTitel("Arg");
      album.setJahr(1998);

      // Ausgabe der XML-Repräsentation des Dokuments
      System.out.println("\nUpdated document:\n");
      System.out.println(album.makeTextDocument());

      }

}

Jetzt muss ChangeAlbum.java noch compiliert und ausgeführt werden und die Daten von album1.xml können geändert werden.

javac ChangeAlbum.java
java ChangeAlbum album1.xml

Original document:

<album id="RHCP-2"><band>Red Hot Chili Peppers</band><titel>Californication</titel>
<jahr>1999</jahr></album>

Updated document:

<album id="DS-1"><band>Die Schnitter</band><titel>Arg</titel><jahr>1998</jahr></album>


RelaxNGCC (RELAX NG Compiler Compiler)

Funktionalität

RelaxNGCC ist ein Parser-Generator (Compiler Compiler) zur Validierung. Das Java-Programm übersetzt eine gegebene kontextfreie Grammatik (RELAX NG-Schema) mit eingebetteten Javacode-Fragmenten in Javacode. Dabei stehen für die beiden Autoren, Daisuke Okajima und Kohsuke Kawaguchi, vor allem die Vorteile der Validierung durch Programmiersprachen im Vordergrund. Dadurch können kontextsensitive Abhängigkeiten unter anderem durch die Benutzung von Bibliotheken, Datenbanken usw. überprüft werden. Bei der Verletzung der Gültigkeit in einem Dokument ist man zudem in der Lage bessere, speziellere Fehlerausschriften auszugeben.

Auch für RelaxNGCC müssen Java 2 und JAXP vorhanden sein.

Beispiel

In stocks.xml werden Aktien mit dem dazugehörigen Aktienmarkt beschrieben, wobei der Inhalt vom ticker-Element auch vom Markt abhängt. Während beim Tokyo Stock Exchange vier Ziffern der Identifikation dienen, sind es beim NASDAQ Großbuchstaben.

<?xml version="1.0" encoding="UTF-8"?>
<stocks>
  <stock>
    <market>Tokyo</market>
    <ticker>6758</ticker>
    <name>Sony</name>
  </stock>
  ...
  <stock>
    <market>NASDAQ</market>
    <ticker>MSFT</ticker>
    <name>Microsoft</name>
  </stock>
</stocks>

Ein dazu passendes RELAX NG-Schema stocks.rng könnte so aussehen:

<?xml version="1.0" encoding="utf-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
   <start>
     <element name="stocks">
       <oneOrMore>
         <element name="stock">
            <element name="market"><text/></element>
            <element name="ticker"><text/></element>
            <element name="name"><text/></element>
         </element>
       </oneOrMore>
     </element>
   </start>
</grammar>

Dieses Schema beschreibt zwar die Struktur von stocks.rng, doch die einzige Möglichkeit zum Ausdrücken der ticker-Abhängigkeit in einem Schema wäre die Aufzählung aller gültigen Paare von market und ticker. Dieses Vorgehen könnte sehr aufwendig werden und deshalb geht RelaxNGCC einen anderen Weg: Die Verifizierung dieser Paare wird z.B. über eine Datenbank mit JDBC abgewickelt.

Aus diesem Ansatz ergibt sich das gleiche RELAX NG-Schema mit eingebettetem Code (stockscc.rng):

<?xml version="1.0" encoding="utf-8"?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0" 
         xmlns:cc="http://www.xml.gr.jp/xmlns/relaxngcc">
  <start cc:class="TickerChecker">
    <cc:java-import>
       import java.sql.*;
    </cc:java-import>
    <cc:java-body>
       Connection _connection;
    </cc:java-body>
    <element name="stocks">
      <oneOrMore>
         <element name="stock">
           <element name="market"><text cc:alias="market"/></element>
           <element name="ticker"><text cc:alias="ticker"/></element>
           <cc:java>
              Statement st = _connection.createStatement();
              ResultSet rs = st.executeQuery("SELECT * FROM stocklist WHERE "+
	        "market='"+market+"' AND ticker='"+ticker+"';");
              if(!rs.next())
                throw new StockNotFoundException();
              st.close();
           </cc:java>
           <element name="name"><text/></element>
         </element>
      </oneOrMore>
    </element>
  </start>
</grammar>

Neben dem hier erklärten RelaxNGCC-Markup (im Namensraum: http://www.xml.gr.jp/xmlns/relaxngcc), gibt es fünf weitere Markup-Bestandteile.

MarkupErklärung
cc:className der erzeugten Klasse ist TickerChecker.
cc:java-importInhalt (<cc:java-import>) wird vor Beginn der Klasse plaziert
cc:java-bodyInhalt wird innerhalb der Klasse plaziert, z.B. Variablen-Deklarationen oder Methoden
cc:aliasWert des text- oder data-Elements wird über diesen Aliasnamen angesprochen
cc:javaInhalt wird während des Parsens ausgeführt

RelaxNGCC übersetzt diese RELAX NG-Grammatik in Java-Quelltext, der das ContentHandler-Interface von SAX implementiert:

RelaxNGCC stockscc.rng

Der erzeugte Quelltext verteilt sich auf folgende 7 Klassen:

Dabei ist TickerChecker.java von besonderem Interesse, da in dieser Klasse der Zustandsautomat implementiert ist, der die Grammatiküberprüfung übernimmt. Der Auszug aus TickerChecker.java zeigt, dass der Inhalt von cc:java beim Verlassen des ticker-Elements ausgeführt wird. Die leaveElement-Methode wird von der endElement-Methode des SAX-ContentHandler aufgerufen.

//import ...
import java.sql.*;
class TickerChecker extends NGCCHandler {
{
    public void leaveElement(String $uri, String $local, String $qname)
    throws SAXException {
        ...
        switch($_ngcc_current_state) {

        ...
        case 6:
            {
                if(($uri.equals("") && $local.equals("ticker"))) {
                    $runtime.onLeaveElementConsumed($uri, $local, $qname);
                    $_ngcc_current_state = 5;
                    Statement st = _connection.createStatement();
                    ResultSet rs = st.executeQuery("SELECT * FROM stocklist WHERE "+
	                 "market='"+market+"' AND ticker='"+ticker+"';");
                    if(!rs.next())
                       throw new StockNotFoundException();
                    st.close();}
                }
                else {
                    unexpectedLeaveElement($qname);
                }
            }
            break;
        default:
            {
                unexpectedLeaveElement($qname);
            }
            break;
        }
    }
    ...
   
    public static void main( String[] args ) throws Exception {
       ...
    }Connection _connection;   
}

Algorithmus

Der Algorithmus erzeugt einen SAX-basierten Parser (Automat) für XML-Dokumente.

Schritt 1: Einteilung des Schemas in Gültigkeitsbereiche (scopes)

Ein Gültigkeitsbereich ist definiert durch ein start- oder durch ein define-Element im Schema. Eine Grammatik enthält ein oder mehrere Gültigkeitsbereiche, für die jeweils ein Automat und eine Java-Klasse erzeugt werden.

Schritt 2: Konstruktion des (deterministischen) Automaten

Das Eingabealphabet, die SAX-Events, erzeugt die Zeichen des Alphabets:

Schritt 3: Hinzufügen der Aktionen

Der eingebettete Code wird hinzugefügt, sodass die einzelnen Code-Fragmente bei bestimmten Zustandsübergängen ausgeführt werden.

Schritt 4: Bildung von FIRST- und FOLLOW-Mengen

Es wird pro Gültigkeitsbereich eine FIRST- und eine FOLLOW-Menge gebildet, wobei ein Element aus FIRST den Start und ein Element aus FOLLOW das Ende eines Gültigkeitsbereiches meldet.

Schritt 5: Überprüfung der Determiniertheit

Diese Überprüfung erfolgt mit den FIRST- und FOLLOW-Mengen.

Beispiel für eine mehrdeutige Grammatik (nichtdeterministisch):

<choice>
<element name="foo">...</element>
<element name="foo">...</element>
</choice>

Schritt 6: Erzeugung des Code

Jetzt kann die Validierung beginnen!


Vergleich zwischen Relaxer und RelaxNGCC

Gemeinsamkeiten

Beide Java-Programme können zur Erzeugung von Java-Quellcode aufgrund einer RELAX NG-Grammatik benutzt werden.

Unterschiede

Merkmal

Relaxer

RelaxNGCC

Einsatzgebiete

einfacher Zugriff auf XML-Strukur,
Erstellung und Umwandlung von Schemata, ...

Validierung

kontextsensitive Validierung

nein

ja

Serialisierung von XML-Dokumenten

ja

nein

Schema-Sprachen

RELAX NG, Relax Core, Relax Namespace

RELAX NG


Quellen

Relaxer:

RelaxNGCC: