Erweiterungsfunktionen mit Java hinzufügen

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten eigene, in Java geschriebene Erweiterungsfunktionen hinzufügen.

Lösung

Da der Mechanismus für die Anbindung des Stylesheets an eine Java-Implementatierung in der Einleitung dieses Kapitels behandelt wurde, konzentriert sich dieser Abschnitt auf die Angabe von Beispielen.

Unter Strings haben Sie gesehen, wie Sie Zahlen zur Basis 10 in solche zu anderen Basen umwandeln können, z.B. zur Basis 16 (hexadezimal). Einen Hex-Konverter können Sie in Java ganz leicht implementieren:

package com.ora.xsltckbk.util;

public class HexConverter
{

  public static String toHex(String intString)
  {
    try
    {
       Integer temp = new Integer(intString) ;
       return new String("0x").concat(Integer.toHexString(temp.intValue( ))) ;
     }
     catch (Exception e)
    {
       return new String("0x0") ;
     }
  }
}

An der Art, wie der Rückgabewert mit einem führenden 0x formatiert wird, können Sie vermutlich erkennen, dass diese spezielle Funktion in einer besonderen Anwendung benutzt wird, bei der es um Code-Generierung geht. Das folgende Beispiel zeigt, wie man sie benutzen könnte:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:hex="xalan://com.ora.xsltckbk.util.HexConverter" exclude-result-prefixes="hex xalan">
  <xsl:template match="group">
    enum <xsl:value-of select="@name"/>
    {
      <xsl:apply-templates mode="enum"/>
    } ;
  </xsl:template>
  <xsl:template match="constant" mode="enum">
    <xsl:variable name="rep">
      <xsl:call-template name="getRep"/>
    </xsl:variable>
    <xsl:value-of select="@name"/> = <xsl:value-of select="$rep"/>
    <xsl:if test="following-sibling::constant">
      <xsl:text>,</xsl:text>
    </xsl:if>
  </xsl:template>
  <xsl:template match="constant">
    <xsl:variable name="rep">
      <xsl:call-template name="getRep"/>
    </xsl:variable>
    const int <xsl:value-of select="@name"/> = <xsl:value-of select="$rep"/> ;
  </xsl:template>
  <xsl:template name="getRep">
    <xsl:choose>
      <xsl:when test="@rep = 'hex'">
        <xsl:value-of select="hex:toHex(@value)"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="@value"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

Das nächste Beispiel zeigt, wie Sie Java-Objekte konstruieren und ihre Methoden aufrufen können. Wenn man XML nach SVG (Scalable Vector Graphics) transformiert, ist es schwierig, mit dem Text-Layout umzugehen. SVG bietet keine Möglichkeit festzustellen, wie breit ein String wird, wenn er gerendert wird. Glücklicherweise bietet Java diese benötigte Funktionalität. Die Frage lautet nur, ob Javas Meinung dazu, wie lang ein String sein wird, wenn er in einer bestimmten Schriftart gerendert wird, mit der Meinung der SVG-Engine übereinstimmt. Nichstdestotrotz ist diese Idee verführerisch genug, um sie auszuprobieren:

package com.ora.xsltckbk.util ;
import java.awt.* ;
import java.awt.geom.* ;
import java.awt.font.* ;
import java.awt.image.*;

public class SVGFontMetrics
{
  public SVGFontMetrics(String fontName, int size)
  {
    m_font = new Font(fontName, Font.PLAIN, size) ;
    BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    m_graphics2D = bi.createGraphics( ) ;
  }

  public SVGFontMetrics(String fontName, int size, boolean bold, boolean italic)
  {
    m_font = new Font(fontName, style(bold,italic) , size) ;
    BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
    m_graphics2D = bi.createGraphics( ) ;
  }

  public double stringWidth(String str)
  {
    FontRenderContext frc = m_graphics2D.getFontRenderContext( );
    TextLayout layout = new TextLayout(str, m_font, frc);
    Rectangle2D rect = layout.getBounds( ) ;
    return rect.getWidth( ) ;
  }

  public double stringHeight(String str)
  {
    FontRenderContext frc = m_graphics2D.getFontRenderContext( );
    TextLayout layout = new TextLayout(str, m_font, frc);
    Rectangle2D rect = layout.getBounds( ) ;
    return rect.getHeight( ) ;
  }

  static private int style(boolean bold, boolean italic)
  {
    int style = Font.PLAIN ;
    if (bold) { style |= Font.BOLD;}
    if (italic) { style |= Font.ITALIC;}
    return style ;
  }

  private Font m_font = null ;
  private Graphics2D m_graphics2D = null;
}

Hierbei bieten die Klassen Graphics2D und TextLayout in Java 2 (JDK 1.3.1) die Informationen, die Sie brauchen. Sie haben zwei Public-Konstruktoren implementiert, um einfache Schriftarten zu unterstützen bzw. solche, die entweder fett oder kursiv sind. Zwei Public-Methoden, stringWidth( ) und stringHeight( ), erhalten Größeninformationen darüber, wie ein bestimmter String in der im Konstruktor angegebenen Schriftart gerendert würde. Diese Technik ist normalerweise bei den meisten benutzten Schriftarten genau genug, aber ohne definitive Garantien werden Sie experimentieren müssen.

Das folgende Stylesheet testet diese Ergebnisse:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:font="xalan://com.ora.xsltckbk.util.SVGFontMetrics" exclude-result-prefixes="font xalan">
  <xsl:output method="xml"/>
  <xsl:template match="/">
    <svg width="100%" height="100%">
      <xsl:apply-templates/>
    </svg>
  </xsl:template>
  <xsl:template match="text">
    <xsl:variable name="fontMetrics" select="font:new(@font, @size, boolean(@weight), boolean(@stytle))"/>
    <xsl:variable name="text" select="."/>
    <xsl:variable name="width" select="font:stringWidth($fontMetrics, $text)"/>
    <xsl:variable name="height" select="font:stringHeight($fontMetrics, $text)"/>
    <xsl:variable name="style">
      <xsl:if test="@style">
        <xsl:value-of select="concat('font-style:',@style)"/>
      </xsl:if>
    </xsl:variable>
    <xsl:variable name="weight">
      <xsl:if test="@weight">
        <xsl:value-of select="concat('font-weight:',@weight)"/>
      </xsl:if>
    </xsl:variable>
    <g style="font-family:{@font};font-size:{@size};{$style};{$weight}">
      <!-- Verwende die SVGFontMetrics-Angaben, um ein Rechteck zu rendern, das leicht größer ist als die erwartete Größe des Texts. Passe die Y-Position je nach vorheriger Textgröße an. -->
      <rect x="10" y="{sum(preceding-sibling::text/@size) * 2}pt" width="{$width + 2}" height="{$height + 2}" style="fill:none; stroke:black; stroke-width:0.5;"/>
      <!-- Platziere den Text so, dass er im Rechteck zentriert ist. -->
      <text x="11" y="{sum(preceding-sibling::text/@size) * 2 + @size div 2 + 2}pt">
        <xsl:value-of select="."/>
      </text>
    </g>
  </xsl:template>
</xsl:stylesheet>
<TextWidthTest>
  <text font="Serif" size="9">Ein M ist BREIT; ein l ist SCHMAL;</text>
  <text font="Serif" size="10">Der Umgang mit Text in SVG ist kein Vergnügen</text>
  <text font="Helvetica" size="12">Und wenn ich ein wenig mit Java mogele...</text>
  <text font="Arial" size="14" weight="bold">VERSPRECHEN SIE, MEINER MUTTER NICHTS ZU SAGEN!</text>
  <text font="Century" size="16" style="italic">Und wenn Sie's tun, dann sei es drum.</text>
  <text font="Courier New" size="18" weight="bold" style="italic">Nur meinem Lektor nehm ich's krumm!</text>
</TextWidthTest>

Wie Sie in der folgenden Abbildung sehen können, hat Ihr Testlauf für einige häufig vorkommende Schriftarten ziemlich gute Ergebnisse produziert.

Erzeugen von Rechtecken mit korrekter Größe um Text herum, mit der Erweiterung SVGFontMetrics

Abbildung: Erzeugen von Rechtecken mit korrekter Größe um Text herum, mit der Erweiterung SVGFontMetrics.

Diskussion

Die im Abschnitt »Lösung« gezeigten Beispiele funktionieren ohne Änderung gleichermaßen mit Xalan und Saxon (trotz des Namensraumes "xml.apache.org/xslt"). Das funktioniert, weil Sie die Abkürzungskonventionen des Prozessors für die Kodierung der Java-Klasse im Namensraum verwendet haben.

Beachten Sie, dass auf die Konstruktoren mit einer Funktion namens new( ) zugegriffen wird und dass die XSLT-Prozessoren mit Hilfe der Argumente feststellen können, welchen überladenen Konstruktor sie aufrufen sollen. Member-Funktionen einer Java-Klasse werden aufgerufen, indem ein zusätzliches erstes Argument übergeben wird, das this entspricht. Das HexConverter-Beispiel zeigt, dass statische Member ohne den zusätzlichen this-Parameter aufgerufen werden.

Das SVGFontMetrics-Beispiel funktioniert nicht mit älteren JDK-Versionen, aber Sie erhalten ähnliche Ergebnisse, wenn Sie die Klasse java.awt.FontMetrics zusammen mit der Klasse java.awt.Graphics benutzen:

package com.ora.xsltckbk.util ;
import java.awt.* ;
import java.awt.geom.* ;
import java.lang.System ;

public class FontMetrics
{
  public FontMetrics(String fontName, int size)
  {
    // Eine beliebige konkrete Komponente reicht aus
    Label component = new Label( ) ;
    m_metrics = component.getFontMetrics(new Font(fontName, Font.PLAIN, size)) ;
    m_graphics = component.getGraphics( ) ;
  }

  public FontMetrics(String fontName, int size, boolean bold, boolean italic)
  {
    // Eine beliebige konkrete Komponente reicht aus
    Label component = new Label( ) ;
    m_metrics = component.getFontMetrics(new Font(fontName, style(bold,italic) , size)) ;
    m_graphics = component.getGraphics( ) ;
  }

  // Einfach, aber bei manchen Schriften ungenau
  public int stringWidth(String str)
  {
    return  m_metrics.stringWidth(str) ;
  }

  // Bessere Genauigkeit bei den meisten Schriften
  public double stringWidthImproved(String str)
  {
    Rectangle2D rect = m_metrics.getStringBounds(str, m_graphics) ;
    return rect.getWidth( ) ;
  }

  static private int style(boolean bold, boolean italic)
  {
    int style = Font.PLAIN ;
    if (bold) { style |= Font.BOLD;}
    if (italic) { style |= Font.ITALIC;}
    return style ;
  }
  private java.awt.FontMetrics m_metrics = null;
  private java.awt.Graphics m_graphics = null ;
}

Auch wenn diese speziellen Beispiele für Ihre unmittelbaren Bedürfnisse ungeeignet sein sollten, so demonstrieren sie dennoch die Mechanismen, die Sie für Ihre eigenen Java-basierten Erweiterungsfunktionen nutzbar machen können. Zu den weiteren Möglichkeiten gehören, mit zunehmendem Schwierigkeitsgrad, folgende:

  1. Verwendung von Hashtable in Java statt xsl:key. Das ermöglicht eine bessere Kontrolle darüber, welche Elemente indiziert werden, und erlaubt es, den Index während der Ausführung zu ändern. Außerdem hebt es die Einschränkung auf, nach der xsl:key-Definitionen keine Variablen referenzieren können. Und Sie können damit einen Master-Index erstellen, der sich über mehrere Dokumente erstreckt.
  2. Implementierung einer Knotensortierfunktion, die die Beschränkungen von xsl:sort kompensiert, z.B. bei einer Sortierung nach den Regeln von Fremdsprachen. Doug Tidwell demonstriert dieses Beispiel mit Saxon in seinem Buch XSLT(O'Reilly, 2001).
  3. Das Lesen und Schreiben mehrerer Dateiformate in einem einzigen Stylesheet. Dabei kann das Stylesheet z.B. Textdateien in anderen Formaten als XML lesen, z.B. CSV oder proprietäre Binärdateien. XSLT 2.0 verfügt über einige Fähigkeiten in diesem Bereich in Form des Elements xsl:result-document (siehe Das Potenzial von XSLT 2.0 ausnutzen).
  4. Verarbeitung von komprimiertem XML direkt aus einer Zip-Datei mit Hilfe von java.util.zip.ZipFile. Dazu wäre es hilfreich, den Quellcode der document-Funktion Ihres XSLT-Prozessors zu studieren.

Siehe auch

XML Abfragen beschäftigte sich mit dem Problem des Layouts von Text in Ihren erzeugten SVG-Baumknoten. Als Bestandteil einer solchen Lösung könnten Sie SVGFontMetrics benutzen.

Entwickler, die sich für Java und SVG interessieren, sollten ruhig einmal einen Blick auf Batik werfen, auch wenn es nicht direkt mit XSLT-Erweiterungen zu tun hat.

  

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