Recipe14.14.Adding Extension Functions Using Java


Recipe 14.14. Adding Extension Functions Using Java

Problem

You want to add your own custom extension functions written in Java.

Solution

This chapter's introduction covered the mechanism for binding the stylesheet to the Java implementations, so this section concentrates on examples.

Chapter 2 showed how to convert numbers from base 10 to other bases (such as base 16 (hex)). You can implement a hex converter in Java easily:

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") ;      }   } }

You can probably tell by the way the return value is formatted with a leading 0x that this particular function will be used in a code-generation application. The following example shows how it might be used:

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

The next example shows how you can construct Java objects and call their methods. Dealing with text layout is difficult when transforming XML to Scalable Vector Graphics. SVG gives you no way to determine how long a string will be when it is rendered. Fortunately, Java provides the functionality you need. The question is whether Java's opinion of how long a string will be when rendered in a particular font matches the SVG engine opinion. Nevertheless, this idea is seductive enough to try:

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

Here Java 2's (JDK 1.3.1) Graphics2D and TextLayout classes provide the information you need. You implemented two public constructors to support simple fonts and fonts that are either bold or italic. Two public methods, stringWidth() and stringHeight() , get dimensional information about how a particular string would be rendered in the font specified by the constructor. This technique is generally accurate on most common fonts, but without precise guarantees, you will have to experiment.

The following stylesheet tests the results:

<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}">     <!-- Use the SVGFontMetrics info to render a rectangle that is -->     <!-- slightly bigger than the expected size of the text. -->     <!-- Adjust the y position based on the previous text size. -->     <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;"/>     <!-- Render the text so it is cenetered in the rectangle -->     <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>

Your test run produced pretty good results on some commonly available fonts, as shown in Figure 14-1.

Figure 14-1. Creating correctly sized text-bounding rectangles with the SVGFontMetrics extension


<TextWidthTest>   <text font="Serif" size="9">M's are BIG; l's are small;</text>   <text font="Serif" size="10">SVG makes handling text no fun at all</text>   <text font="Helvetica" size="12">But if I cheat with a little Java</text>   <text font="Arial" size="14" weight="bold">PROMISE ME YOU WON'T TELL MY MAMMA!   </text>   <text font="Century" size="16" style="italic">But if you do, I won't lose cheer.   </text>   <text font="Courier New" size="18" weight="bold" style="italic">Its really my tech editor that I fear!</text> </TextWidthTest>

Discussion

The examples shown in the "Solution" section work unchanged with either Xalan or Saxon (despite the xml.apache.org/xslt namespace). It works because you used the processors' shortcut conventions for encoding the Java class in the namespace.

Notice that constructors are accessed using a function with the name new( ) and that the XSLT processors can figure out which overloaded constructor to call based on the arguments. Member functions of a Java class are called by passing an extra initial argument corresponding to this. The HexConverter example shows that static members are called without the extra this parameter.

The SVGFontMetrics example does not work with older versions of the JDK, but similar results can be obtained if you use the java.awt.FontMetrics class in conjunction with the original java.awt.Graphics class:

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)   {     //Any concrete component will do     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)   {     //Any concrete component will do     Label component = new Label( ) ;     m_metrics       = component.getFontMetrics(            new Font(fontName, style(bold,italic) , size)) ;     m_graphics = component.getGraphics( ) ;   }       //Simple, but less accurate on some fonts   public int stringWidth(String str)   {     return  m_metrics.stringWidth(str) ;   }       //Better accuracy on most fonts   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 ; }

Although these particular examples may not fulfill your immediate needs, they demonstrate the mechanisms by which you can harness your own Java-based extension functions. Other possibilities, in increasing level of difficulty, include:

  1. Using Java's Hashtable instead of xsl:key. This allows better control over which elements are indexed and allows the index to be changed during the execution. It overcomes the limitation, whereas xsl:key definitions cannot reference variables. You can also use it to build a master index that spans multiple documents.

  2. Implementing a node-sorting function that can compensate for xsl:sort's limitations, for example, constructing a sort based on foreign language rules. Doug Tidwell demonstrates this example with Saxon in XSLT (O'Reilly, 2001).

  3. Reads and writes multiple file formats in a single stylesheet. For example, it allows the stylesheet to read text files other than XML, such as CSV or proprietary binary files. XSLT 2.0 provides capabilities in this area with the xsl:result-document and element (see Chapter 6).

  4. Processes compressed XML straight from a zip fileusing java.util.zip.ZipFile. Studying the source code of your XSLT processor's document function would be helpful.

See Also

Chapter 9 punted on the problem of laying out text within your generated SVG tree nodes. You could use SVGFontMetrics as an ingredient in the solution.

Although not specifically related to XSLT extensions, developers interested in Java and SVG should check out Batik (http://xml.apache.org/batik/index.html).




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net