From e931eb4f3b15f965c166ea8996a386f04d4dd6fb Mon Sep 17 00:00:00 2001 From: Norman Walsh Date: Thu, 4 May 2006 13:23:51 +0000 Subject: [PATCH] Support Xalan 2.7 --- xsl/extensions/xalan27/.cvsignore | 2 + xsl/extensions/xalan27/build.xml | 69 ++ xsl/extensions/xalan27/nbproject/.cvsignore | 1 + .../xalan27/nbproject/build-impl.xml | 541 ++++++++++ .../xalan27/nbproject/genfiles.properties | 8 + .../xalan27/nbproject/project.properties | 56 ++ xsl/extensions/xalan27/nbproject/project.xml | 16 + .../xalan27/src/com/nwalsh/xalan/CVS.java | 90 ++ .../xalan27/src/com/nwalsh/xalan/Callout.java | 143 +++ .../src/com/nwalsh/xalan/FormatCallout.java | 101 ++ .../nwalsh/xalan/FormatDingbatCallout.java | 82 ++ .../nwalsh/xalan/FormatGraphicCallout.java | 83 ++ .../com/nwalsh/xalan/FormatTextCallout.java | 38 + .../nwalsh/xalan/FormatUnicodeCallout.java | 87 ++ .../xalan27/src/com/nwalsh/xalan/Func.java | 59 ++ .../src/com/nwalsh/xalan/ImageIntrinsics.java | 160 +++ .../xalan27/src/com/nwalsh/xalan/Params.java | 58 ++ .../xalan27/src/com/nwalsh/xalan/Table.java | 531 ++++++++++ .../xalan27/src/com/nwalsh/xalan/Text.java | 205 ++++ .../src/com/nwalsh/xalan/Verbatim.java | 926 ++++++++++++++++++ 20 files changed, 3256 insertions(+) create mode 100644 xsl/extensions/xalan27/.cvsignore create mode 100644 xsl/extensions/xalan27/build.xml create mode 100644 xsl/extensions/xalan27/nbproject/.cvsignore create mode 100644 xsl/extensions/xalan27/nbproject/build-impl.xml create mode 100644 xsl/extensions/xalan27/nbproject/genfiles.properties create mode 100644 xsl/extensions/xalan27/nbproject/project.properties create mode 100644 xsl/extensions/xalan27/nbproject/project.xml create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/CVS.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Callout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatCallout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Func.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Params.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Table.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Text.java create mode 100644 xsl/extensions/xalan27/src/com/nwalsh/xalan/Verbatim.java diff --git a/xsl/extensions/xalan27/.cvsignore b/xsl/extensions/xalan27/.cvsignore new file mode 100644 index 000000000..9d0b71a3c --- /dev/null +++ b/xsl/extensions/xalan27/.cvsignore @@ -0,0 +1,2 @@ +build +dist diff --git a/xsl/extensions/xalan27/build.xml b/xsl/extensions/xalan27/build.xml new file mode 100644 index 000000000..c39733b62 --- /dev/null +++ b/xsl/extensions/xalan27/build.xml @@ -0,0 +1,69 @@ + + + + + + Builds, tests, and runs the project xalan27. + + + diff --git a/xsl/extensions/xalan27/nbproject/.cvsignore b/xsl/extensions/xalan27/nbproject/.cvsignore new file mode 100644 index 000000000..3e18ebf09 --- /dev/null +++ b/xsl/extensions/xalan27/nbproject/.cvsignore @@ -0,0 +1 @@ +private diff --git a/xsl/extensions/xalan27/nbproject/build-impl.xml b/xsl/extensions/xalan27/nbproject/build-impl.xml new file mode 100644 index 000000000..fa2628e4d --- /dev/null +++ b/xsl/extensions/xalan27/nbproject/build-impl.xml @@ -0,0 +1,541 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + + + + + + java -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + diff --git a/xsl/extensions/xalan27/nbproject/genfiles.properties b/xsl/extensions/xalan27/nbproject/genfiles.properties new file mode 100644 index 000000000..fd61ced98 --- /dev/null +++ b/xsl/extensions/xalan27/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=f1bed978 +build.xml.script.CRC32=3c6d9e2a +build.xml.stylesheet.CRC32=d5b6853a +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=f1bed978 +nbproject/build-impl.xml.script.CRC32=df1e1cd8 +nbproject/build-impl.xml.stylesheet.CRC32=99b91518 diff --git a/xsl/extensions/xalan27/nbproject/project.properties b/xsl/extensions/xalan27/nbproject/project.properties new file mode 100644 index 000000000..f6e21137a --- /dev/null +++ b/xsl/extensions/xalan27/nbproject/project.properties @@ -0,0 +1,56 @@ +application.args= +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +debug.classpath=\ + ${run.classpath} +debug.test.classpath=\ + ${run.test.classpath} +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/xalan27.jar +dist.javadoc.dir=${dist.dir}/javadoc +file.reference.xalan.jar=../../../../../usr/local/java/xalan2/xalan.jar +jar.compress=false +javac.classpath=\ + ${file.reference.xalan.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.source=1.5 +javac.target=1.5 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir}:\ + ${libs.junit.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding= +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +meta.inf.dir=${src.dir}/META-INF +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project +# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value +# or test-sys-prop.name=value to set system properties for unit tests): +run.jvmargs= +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +src.dir=src +test.src.dir=test diff --git a/xsl/extensions/xalan27/nbproject/project.xml b/xsl/extensions/xalan27/nbproject/project.xml new file mode 100644 index 000000000..687ff026d --- /dev/null +++ b/xsl/extensions/xalan27/nbproject/project.xml @@ -0,0 +1,16 @@ + + + org.netbeans.modules.java.j2seproject + + + xalan27 + 1.6.5 + + + + + + + + + diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/CVS.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/CVS.java new file mode 100644 index 000000000..fbd190e90 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/CVS.java @@ -0,0 +1,90 @@ +package com.nwalsh.xalan; + +import java.io.*; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; +import java.text.DateFormat; +import java.text.ParseException; + +/** + *

Xalan extension to convert CVS date strings into local time

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Xalan + * extension to turn the CVS date strings, which are UTC:

+ * + *
$Date: 2000/11/09 02:34:20 $
+ * + *

into legibly formatted local time:

+ * + *
Wed Nov 08 18:34:20 PST 2000
+ * + *

(I happened to be in California when I wrote this documentation.)

+ + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class CVS { + /** + *

Constructor for CVS

+ * + *

All of the methods are static, so the constructor does nothing.

+ */ + public CVS() { + } + + /** + *

Convert a CVS date string into local time.

+ * + * @param cvsDate The CVS date string. + * + * @return The date, converted to local time and reformatted. + */ + public String localTime (String cvsDate) { + // A cvsDate has the following form "$Date$" + if (!cvsDate.startsWith("$Date: ")) { + return cvsDate; + } + + String yrS = cvsDate.substring(7,11); + String moS = cvsDate.substring(12,14); + String daS = cvsDate.substring(15,17); + String hrS = cvsDate.substring(18,20); + String miS = cvsDate.substring(21,23); + String seS = cvsDate.substring(24,26); + + TimeZone tz = TimeZone.getTimeZone("GMT+0"); + GregorianCalendar gmtCal = new GregorianCalendar(tz); + + try { + gmtCal.set(Integer.parseInt(yrS), + Integer.parseInt(moS)-1, + Integer.parseInt(daS), + Integer.parseInt(hrS), + Integer.parseInt(miS), + Integer.parseInt(seS)); + } catch (NumberFormatException e) { + // nop + } + + Date d = gmtCal.getTime(); + + return d.toString(); + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Callout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Callout.java new file mode 100644 index 000000000..920cfc3bf --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Callout.java @@ -0,0 +1,143 @@ +package com.nwalsh.xalan; + +import org.w3c.dom.*; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class is just for book keeping in the Verbatim class. + * It stores information about the location of callouts.

+ * + *

Only line/column based callouts are supported. This class + * implements the Comparable interface so that callouts can be sorted. + * Callouts are sorted so that they occur in left-to-right, + * top-to-bottom order based on line/column.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + * */ +public class Callout implements Comparable { + /** The callout number. */ + private int callout = 0; + /** The area Element item that generated this callout. */ + private Element area = null; + /** The line on which this callout occurs. */ + private int line = 0; + /** The column in which this callout appears. */ + private int col = 0; + /** The type of callout. */ + private int type = 0; + /** The other type of callout. */ + private String otherType = null; + + public static final int CALS_PAIR = 1; + public static final int LINE_COLUMN = 2; + public static final int LINE_COLUMN_PAIR = 3; + public static final int LINE_RANGE = 4; + public static final int OTHER = 5; + + /** The constructor; initialize the private data structures. */ + public Callout(int callout, Element area, int line, int col, int type) { + this.callout = callout; + this.area = area; + this.line = line; + this.col = col; + this.type = type; + this.otherType = null; + } + + /** The constructor; initialize the private data structures. */ + public Callout(int callout, Element area, int line, int col, String otherType) { + this.callout = callout; + this.area = area; + this.line = line; + this.col = col; + this.type = Callout.OTHER; + this.otherType = otherType; + } + + /** + *

The compareTo method compares this Callout with another.

+ * + *

Given two Callouts, A and B, A < B if:

+ * + *
    + *
  1. A.line < B.line, or
  2. + *
  3. A.line = B.line && A.col < B.col, or
  4. + *
  5. A.line = B.line && A.col = B.col && A.callout < B.callout
  6. + *
  7. Otherwise, they're equal.
  8. + *
+ */ + public int compareTo (Object o) { + Callout c = (Callout) o; + + if (line == c.getLine()) { + if (col > c.getColumn()) { + return 1; + } else if (col < c.getColumn()) { + return -1; + } else { + if (callout < c.getCallout()) { + return -1; + } else if (callout > c.getCallout()) { + return 1; + } else { + return 0; + } + } + } else { + if (line > c.getLine()) { + return 1; + } else { + return -1; + } + } + } + + /** Access the Callout's area. */ + public Element getArea() { + return area; + } + + /** Access the Callout's line. */ + public int getLine() { + return line; + } + + /** Access the Callout's column. */ + public int getColumn() { + return col; + } + + /** Access the Callout's callout number. */ + public int getCallout() { + return callout; + } + + /** Access the Callout's type. */ + public int getType() { + return type; + } + + /** Access the Callout's otherType. */ + public String getOtherType() { + return otherType; + } + + +} + diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatCallout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatCallout.java new file mode 100644 index 000000000..ff3018b95 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatCallout.java @@ -0,0 +1,101 @@ +package com.nwalsh.xalan; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import org.w3c.dom.*; +import org.apache.xml.utils.DOMBuilder; +import com.nwalsh.xalan.Callout; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2001 Norman Walsh.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + **/ + +public abstract class FormatCallout { + protected static final String foURI = "http://www.w3.org/1999/XSL/Format"; + protected static final String xhURI = "http://www.w3.org/1999/xhtml"; + protected boolean stylesheetFO = false; + + public FormatCallout() { + //nop; + } + + public String areaLabel(Element area) { + String label = null; + + if (!"".equals(area.getAttribute("label"))) { + // If this area has a label, use it + label = area.getAttribute("label"); + } else { + // Otherwise, if its parent is an areaset and it has a label, use that + Element parent = (Element) area.getParentNode(); + if (parent != null + && parent.getNodeName().equals("areaset") + && !"".equals(parent.getAttribute("label"))) { + label = parent.getAttribute("label"); + } + } + + return label; + } + + public void startSpan(DOMBuilder rtf) + throws SAXException { + // no point in doing this for FO, right? + if (!stylesheetFO) { + AttributesImpl spanAttr = new AttributesImpl(); + spanAttr.addAttribute("", "class", "class", "CDATA", "co"); + rtf.startElement("", "span", "span", spanAttr); + } + } + + public void endSpan(DOMBuilder rtf) + throws SAXException { + // no point in doing this for FO, right? + if (!stylesheetFO) { + rtf.endElement("", "span", "span"); + } + } + + public void formatTextCallout(DOMBuilder rtf, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String userLabel = areaLabel(area); + String label = "(" + num + ")"; + + if (userLabel != null) { + label = userLabel; + } + + char chars[] = label.toCharArray(); + + try { + startSpan(rtf); + rtf.characters(chars, 0, label.length()); + endSpan(rtf); + } catch (SAXException e) { + System.out.println("SAX Exception in text formatCallout"); + } + } + + public abstract void formatCallout(DOMBuilder rtf, + Callout callout); +} + diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java new file mode 100644 index 000000000..e78d3cc1d --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java @@ -0,0 +1,82 @@ +package com.nwalsh.xalan; + +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.SAXException; +import org.w3c.dom.*; +import org.apache.xml.utils.DOMBuilder; +import com.nwalsh.xalan.Callout; +import org.apache.xml.utils.AttList; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2001 Norman Walsh.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + **/ + +public class FormatDingbatCallout extends FormatCallout { + int graphicsMax = 0; + + public FormatDingbatCallout(int max, boolean fo) { + graphicsMax = max; + stylesheetFO = fo; + } + + public void formatCallout(DOMBuilder rtf, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String label = areaLabel(area); + + try { + if (label == null && num <= graphicsMax) { + AttributesImpl imgAttr = new AttributesImpl(); + String ns = ""; + String prefix = ""; + String imgName = ""; + + if (stylesheetFO) { + ns = foURI; + prefix = "fo:"; // FIXME: this could be a problem... + imgName = "inline"; + imgAttr.addAttribute("", "font-family", "font-family", "CDATA", + "ZapfDingbats"); + } else { + ns = ""; + prefix = ""; + imgName = "font"; + imgAttr.addAttribute("", "face", "face", "CDATA", + "ZapfDingbats"); + } + + startSpan(rtf); + rtf.startElement(ns, imgName, prefix+imgName, imgAttr); + + char chars[] = new char[1]; + chars[0] = (char) (0x2775 + num); + rtf.characters(chars, 0, 1); + + rtf.endElement(ns, imgName, prefix+imgName); + endSpan(rtf); + } else { + formatTextCallout(rtf, callout); + } + } catch (SAXException e) { + System.out.println("SAX Exception in graphics formatCallout"); + } + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java new file mode 100644 index 000000000..a4f83bd41 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java @@ -0,0 +1,83 @@ +package com.nwalsh.xalan; + +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.SAXException; +import org.w3c.dom.*; +import org.apache.xml.utils.DOMBuilder; +import com.nwalsh.xalan.Callout; +import org.apache.xml.utils.AttList; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2001 Norman Walsh.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + **/ + +public class FormatGraphicCallout extends FormatCallout { + String graphicsPath = ""; + String graphicsExt = ""; + int graphicsMax = 0; + + public FormatGraphicCallout(String path, String ext, int max, boolean fo) { + graphicsPath = path; + graphicsExt = ext; + graphicsMax = max; + stylesheetFO = fo; + } + + public void formatCallout(DOMBuilder rtf, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String label = areaLabel(area); + + try { + if (label == null && num <= graphicsMax) { + AttributesImpl imgAttr = new AttributesImpl(); + String ns = ""; + String prefix = ""; + String imgName = ""; + + if (stylesheetFO) { + ns = foURI; + prefix = "fo:"; // FIXME: this could be a problem... + imgName = "external-graphic"; + imgAttr.addAttribute("", "src", "src", "CDATA", + graphicsPath + num + graphicsExt); + imgAttr.addAttribute("", "alt", "alt", "CDATA", label); + } else { + ns = ""; + prefix = ""; + imgName = "img"; + imgAttr.addAttribute("", "src", "src", "CDATA", + graphicsPath + num + graphicsExt); + imgAttr.addAttribute("", "alt", "alt", "CDATA", label); + } + + startSpan(rtf); + rtf.startElement(ns, imgName, prefix+imgName, imgAttr); + rtf.endElement(ns, imgName, prefix+imgName); + endSpan(rtf); + } else { + formatTextCallout(rtf, callout); + } + } catch (SAXException e) { + System.out.println("SAX Exception in graphics formatCallout"); + } + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java new file mode 100644 index 000000000..8cdbef541 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java @@ -0,0 +1,38 @@ +package com.nwalsh.xalan; + +import org.w3c.dom.*; +import org.apache.xml.utils.DOMBuilder; +import com.nwalsh.xalan.Callout; +import org.apache.xml.utils.AttList; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2001 Norman Walsh.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + **/ + +public class FormatTextCallout extends FormatCallout { + public FormatTextCallout(boolean fo) { + stylesheetFO = fo; + } + + public void formatCallout(DOMBuilder rtf, + Callout callout) { + formatTextCallout(rtf, callout); + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java new file mode 100644 index 000000000..2fdaf54ce --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java @@ -0,0 +1,87 @@ +package com.nwalsh.xalan; + +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.SAXException; +import org.w3c.dom.*; +import org.apache.xml.utils.DOMBuilder; +import com.nwalsh.xalan.Callout; +import org.apache.xml.utils.AttList; + +/** + *

Utility class for the Verbatim extension (ignore this).

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2001 Norman Walsh.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @see Verbatim + * + * @version $Id$ + **/ + +public class FormatUnicodeCallout extends FormatCallout { + int unicodeMax = 0; + int unicodeStart = 0; + String unicodeFont = ""; + + public FormatUnicodeCallout(String font, int start, int max, boolean fo) { + unicodeFont = font; + unicodeMax = max; + unicodeStart = start; + stylesheetFO = fo; + } + + public void formatCallout(DOMBuilder rtf, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String label = areaLabel(area); + + try { + if (label == null && num <= unicodeMax) { + AttributesImpl inAttr = new AttributesImpl(); + String ns = ""; + String prefix = ""; + String inName = ""; + + if (!unicodeFont.equals("")) { + if (stylesheetFO) { + ns = foURI; + prefix = "fo:"; + inName = "inline"; + inAttr.addAttribute("", "", "font-family", "CDATA", unicodeFont); + } else { + inName = "font"; + inAttr.addAttribute("", "", "face", "CDATA", unicodeFont); + } + } + + char chars[] = new char[1]; + chars[0] = (char) (unicodeStart + num - 1); + + startSpan(rtf); + if (!unicodeFont.equals("")) { + rtf.startElement(ns, inName, prefix+inName, inAttr); + } + rtf.characters(chars, 0, 1); + if (!unicodeFont.equals("")) { + rtf.endElement(ns, inName, prefix+inName); + } + endSpan(rtf); + } else { + formatTextCallout(rtf, callout); + } + } catch (SAXException e) { + System.out.println("SAX Exception in unicode formatCallout"); + } + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Func.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Func.java new file mode 100644 index 000000000..e03ae6bd2 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Func.java @@ -0,0 +1,59 @@ +// Func - Xalann extension function test + +package com.nwalsh.xalan; + +import org.xml.sax.SAXException; +import org.xml.sax.AttributeList; +import org.xml.sax.ContentHandler; + +import org.w3c.dom.*; +import org.w3c.dom.traversal.NodeIterator; +import org.apache.xerces.dom.*; + +import org.apache.xpath.objects.XObject; +import org.apache.xpath.objects.XRTreeFrag; +import org.apache.xpath.XPath; +import org.apache.xpath.NodeSet; +import org.apache.xalan.extensions.XSLProcessorContext; +import org.apache.xalan.extensions.ExpressionContext; +import org.apache.xalan.transformer.TransformerImpl; +import org.apache.xalan.templates.StylesheetRoot; +import org.apache.xalan.templates.ElemExtensionCall; +import org.apache.xalan.templates.OutputProperties; +import org.apache.xalan.res.XSLTErrorResources; + +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.TransformerException; + +public class Func { + public Func() { + } + + public DocumentFragment doSomething(NodeIterator rtf) { + System.out.println("Got here 2: " + rtf); + + DocumentFragment df = (DocumentFragment) rtf.nextNode(); + Element node = (Element) df.getFirstChild(); + + System.out.println("node=" + node); + System.out.println("namesp uri: " + node.getNamespaceURI()); + System.out.println("local name: " + node.getLocalName()); + + return df; + } + + public DocumentFragment doSomething(DocumentFragment rtf) { + System.out.println("Got here: " + rtf); + + return rtf; + /* + Element node = (Element) rtf.getFirstChild(); + + System.out.println("node=" + node); + System.out.println("namesp uri: " + node.getNamespaceURI()); + System.out.println("local name: " + node.getLocalName()); + + return rtf; + */ + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java new file mode 100644 index 000000000..94f02f4c0 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java @@ -0,0 +1,160 @@ +package com.nwalsh.xalan; + +import java.io.*; +import java.awt.*; +import java.awt.image.*; +import java.lang.Thread; +import java.util.StringTokenizer; + +import org.apache.xalan.extensions.ExpressionContext; + +/** + *

Xalan extension to examine intrinsic size of images

+ * + *

$Id$

+ * + *

Copyright (C) 2002 Norman Walsh.

+ * + *

This class provides a + * Xalan + * extension to find the intrinsic size of images.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class ImageIntrinsics implements ImageObserver { + boolean imageLoaded = false; + boolean imageFailed = false; + Image image = null; + int width = -1; + int depth = -1; + + /** + *

Constructor for ImageIntrinsics

+ */ + public ImageIntrinsics(ExpressionContext context, String imageFn) { + System.setProperty("java.awt.headless","true"); + image = Toolkit.getDefaultToolkit().getImage (imageFn); + width = image.getWidth(this); + + while (!imageFailed && (width == -1 || depth == -1)) { + try { + java.lang.Thread.currentThread().sleep(50); + } catch (Exception e) { + // nop; + } + width = image.getWidth(this); + depth = image.getHeight(this); + } + + if (imageFailed) { + // Maybe it's an EPS or PDF? + // FIXME: this code is crude + BufferedReader ir = null; + String line = null; + int lineLimit = 100; + + try { + ir = new BufferedReader(new FileReader(new File(imageFn))); + line = ir.readLine(); + + if (line != null && line.startsWith("%PDF-")) { + // We've got a PDF! + while (lineLimit > 0 && line != null) { + lineLimit--; + if (line.startsWith("/CropBox [")) { + line = line.substring(10); + if (line.indexOf("]") >= 0) { + line = line.substring(0, line.indexOf("]")); + } + parseBox(line); + lineLimit = 0; + } + line = ir.readLine(); + } + } else if (line != null && line.startsWith("%!") && line.indexOf(" EPSF-") > 0) { + // We've got an EPS! + while (lineLimit > 0 && line != null) { + lineLimit--; + if (line.startsWith("%%BoundingBox: ")) { + line = line.substring(15); + parseBox(line); + lineLimit = 0; + } + line = ir.readLine(); + } + } + } catch (Exception e) { + // nop; + } + + if (ir != null) { + try { + ir.close(); + } catch (Exception e) { + // nop; + } + } + } + } + + public int getWidth(ExpressionContext context, int defaultWidth) { + if (width >= 0) { + return width; + } else { + return defaultWidth; + } + } + + public int getDepth(ExpressionContext context, int defaultDepth) { + if (depth >= 0) { + return depth; + } else { + return defaultDepth; + } + } + + private void parseBox(String line) { + int [] corners = new int [4]; + int count = 0; + + StringTokenizer st = new StringTokenizer(line); + while (count < 4 && st.hasMoreTokens()) { + try { + corners[count++] = Integer.parseInt(st.nextToken()); + } catch (Exception e) { + // nop; + } + } + + width = corners[2] - corners[0]; + depth = corners[3] - corners[1]; + } + + public boolean imageUpdate(Image img, int infoflags, + int x, int y, int width, int height) { + if ((infoflags & ImageObserver.ERROR) == ImageObserver.ERROR) { + imageFailed = true; + return false; + } + + // I really only care about the width and height, but if I return false as + // soon as those are available, the BufferedInputStream behind the loader + // gets closed too early. + int flags = ImageObserver.ALLBITS; + if ((infoflags & flags) == flags) { + return false; + } else { + return true; + } + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Params.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Params.java new file mode 100644 index 000000000..20261261d --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Params.java @@ -0,0 +1,58 @@ +// Params.java - Read stylesheet parameters in Xalan + +package com.nwalsh.xalan; + +import org.apache.xpath.objects.XObject; +import org.apache.xpath.XPathContext; +import org.apache.xalan.extensions.ExpressionContext; +import org.apache.xml.utils.QName; + +import javax.xml.transform.TransformerException; + +public class Params { + + public static String getString(ExpressionContext context, + String varName) { + try { + XObject var = context.getVariableOrParam(new QName(varName)); + if (var != null) { + return var.toString(); + } else { + System.out.println("$" + varName + " is not a defined parameter."); + return ""; + } + } catch (TransformerException te) { + // Nevermind the warning + // System.out.println("Transformer exception getting value of $" + varName); + return ""; + } + } + + public static int getInt(ExpressionContext context, + String varName) { + String stringValue = getString(context, varName); + if (stringValue != null) { + try { + int value = Integer.parseInt(stringValue); + return value; + } catch (NumberFormatException e) { + System.out.println("$" + varName + " is not an integer."); + } + } + return 0; + } + + public static boolean getBoolean(ExpressionContext context, + String varName) { + String stringValue = getString(context, varName); + if (stringValue != null) { + if (stringValue.equals("0") || stringValue.equals("")) { + return false; + } else { + return true; + } + } else { + return false; + } + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Table.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Table.java new file mode 100644 index 000000000..19b2aea38 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Table.java @@ -0,0 +1,531 @@ +// Verbatim.java - Xalan extensions supporting DocBook verbatim environments + +package com.nwalsh.xalan; + +import java.util.Hashtable; +import org.xml.sax.*; +import org.xml.sax.helpers.AttributesImpl; +import org.w3c.dom.*; +import org.w3c.dom.traversal.NodeIterator; + +import javax.xml.transform.TransformerException; + +import org.apache.xpath.objects.XObject; +import org.apache.xpath.XPathContext; +import org.apache.xalan.extensions.ExpressionContext; +import org.apache.xml.utils.DOMBuilder; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.apache.xml.utils.DOMHelper; +import org.apache.xml.utils.QName; +import org.apache.xml.utils.AttList; + +/** + *

Xalan extensions supporting Tables

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Xalan + * implementation of some code to adjust CALS Tables to HTML + * Tables.

+ * + *

Column Widths

+ *

The adjustColumnWidths method takes a result tree + * fragment (assumed to contain the colgroup of an HTML Table) + * and returns the result tree fragment with the column widths + * adjusted to HTML terms.

+ * + *

Convert Lengths

+ *

The convertLength method takes a length specification + * of the form 9999.99xx (where "xx" is a unit) and returns that length + * as an integral number of pixels. For convenience, percentage lengths + * are returned unchanged.

+ *

The recognized units are: inches (in), centimeters (cm), + * millimeters (mm), picas (pc, 1pc=12pt), points (pt), and pixels (px). + * A number with no units is assumed to be pixels.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class Table { + /** The number of pixels per inch */ + private static int pixelsPerInch = 96; + + /** The hash used to associate units with a length in pixels. */ + protected static Hashtable unitHash = null; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** + *

Constructor for Verbatim

+ * + *

All of the methods are static, so the constructor does nothing.

+ */ + public Table() { + } + + /** Initialize the internal hash table with proper values. */ + protected static void initializeHash() { + unitHash = new Hashtable(); + unitHash.put("in", new Float(pixelsPerInch)); + unitHash.put("cm", new Float(pixelsPerInch / 2.54)); + unitHash.put("mm", new Float(pixelsPerInch / 25.4)); + unitHash.put("pc", new Float((pixelsPerInch / 72) * 12)); + unitHash.put("pt", new Float(pixelsPerInch / 72)); + unitHash.put("px", new Float(1)); + } + + /** Set the pixels-per-inch value. Only positive values are legal. */ + public static void setPixelsPerInch(int value) { + if (value > 0) { + pixelsPerInch = value; + initializeHash(); + } + } + + /** Return the current pixels-per-inch value. */ + public int getPixelsPerInch() { + return pixelsPerInch; + } + + /** + *

Convert a length specification to a number of pixels.

+ * + *

The specified length should be of the form [+/-]999.99xx, + * where xx is a valid unit.

+ */ + public static int convertLength(String length) { + // The format of length should be 999.999xx + int sign = 1; + String digits = ""; + String units = ""; + char lench[] = length.toCharArray(); + float flength = 0; + boolean done = false; + int pos = 0; + float factor = 1; + int pixels = 0; + + if (unitHash == null) { + initializeHash(); + } + + if (lench[pos] == '+' || lench[pos] == '-') { + if (lench[pos] == '-') { + sign = -1; + } + pos++; + } + + while (!done) { + if (pos >= lench.length) { + done = true; + } else { + if ((lench[pos] > '9' || lench[pos] < '0') && lench[pos] != '.') { + done = true; + units = length.substring(pos); + } else { + digits += lench[pos++]; + } + } + } + + try { + flength = Float.parseFloat(digits); + } catch (NumberFormatException e) { + System.out.println(digits + " is not a number; 1 used instead."); + flength = 1; + } + + Float f = null; + + if (!units.equals("")) { + f = (Float) unitHash.get(units); + if (f == null) { + System.out.println(units + " is not a known unit; 1 used instead."); + factor = 1; + } else { + factor = f.floatValue(); + } + } else { + factor = 1; + } + + f = new Float(flength * factor); + + pixels = f.intValue() * sign; + + return pixels; + } + + /** + *

Adjust column widths in an HTML table.

+ * + *

The specification of column widths in CALS (a relative width + * plus an optional absolute width) are incompatible with HTML column + * widths. This method adjusts CALS column width specifiers in an + * attempt to produce equivalent HTML specifiers.

+ * + *

In order for this method to work, the CALS width specifications + * should be placed in the "width" attribute of the <col>s within + * a <colgroup>. Then the colgroup result tree fragment is passed + * to this method.

+ * + *

This method makes use of two parameters from the XSL stylesheet + * that calls it: nominal.table.width and + * table.width. The value of nominal.table.width + * must be an absolute distance. The value of table.width + * can be either absolute or relative.

+ * + *

Presented with a mixture of relative and + * absolute lengths, the table width is used to calculate + * appropriate values. If the table.width is relative, + * the nominal width is used for this calculation.

+ * + *

There are three possible combinations of values:

+ * + *
    + *
  1. There are no relative widths; in this case the absolute widths + * are used in the HTML table.
  2. + *
  3. There are no absolute widths; in this case the relative widths + * are used in the HTML table.
  4. + *
  5. There are a mixture of absolute and relative widths: + *
      + *
    1. If the table width is absolute, all widths become absolute.
    2. + *
    3. If the table width is relative, make all the widths absolute + * relative to the nominal table width then turn them all + * back into relative widths.
    4. + *
    + *
  6. + *
+ * + * @param context The stylesheet context; supplied automatically by Xalan + * @param xalanNI + * + * @return The result tree fragment containing the adjusted colgroup. + * + */ + + public DocumentFragment adjustColumnWidths (ExpressionContext context, + NodeIterator xalanNI) { + + int nominalWidth = convertLength(Params.getString(context, + "nominal.table.width")); + String tableWidth = Params.getString(context, "table.width"); + String styleType = Params.getString(context, "stylesheet.result.type"); + boolean foStylesheet = styleType.equals("fo"); + + DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode(); + Element colgroup = (Element) xalanRTF.getFirstChild(); + + // N.B. ...stree.ElementImpl doesn't implement getElementsByTagName() + + Node firstCol = null; + // If this is an FO tree, there might be no colgroup... + if (colgroup.getLocalName().equals("colgroup")) { + firstCol = colgroup.getFirstChild(); + } else { + firstCol = colgroup; + } + + // Count the number of columns... + Node child = firstCol; + int numColumns = 0; + while (child != null) { + if (child.getNodeType() == Node.ELEMENT_NODE + && (child.getNodeName().equals("col") + || (child.getNamespaceURI().equals(foURI) + && child.getLocalName().equals("table-column")))) { + numColumns++; + } + + child = child.getNextSibling(); + } + + String widths[] = new String[numColumns]; + Element columns[] = new Element[numColumns]; + int colnum = 0; + + child = firstCol; + while (child != null) { + if (child.getNodeType() == Node.ELEMENT_NODE + && (child.getNodeName().equals("col") + || (child.getNamespaceURI().equals(foURI) + && child.getLocalName().equals("table-column")))) { + Element col = (Element) child; + + columns[colnum] = col; + + if (foStylesheet) { + if ("".equals(col.getAttribute("column-width"))) { + widths[colnum] = "1*"; + } else { + widths[colnum] = col.getAttribute("column-width"); + } + } else { + if ("".equals(col.getAttribute("width"))) { + widths[colnum] = "1*"; + } else { + widths[colnum] = col.getAttribute("width"); + } + } + + colnum++; + } + child = child.getNextSibling(); + } + + float relTotal = 0; + float relParts[] = new float[numColumns]; + + float absTotal = 0; + float absParts[] = new float[numColumns]; + + for (int count = 0; count < numColumns; count++) { + String width = widths[count]; + int pos = width.indexOf("*"); + if (pos >= 0) { + String relPart = width.substring(0, pos); + String absPart = width.substring(pos+1); + + try { + float rel = Float.parseFloat(relPart); + relTotal += rel; + relParts[count] = rel; + } catch (NumberFormatException e) { + System.out.println(relPart + " is not a valid relative unit."); + } + + int pixels = 0; + if (absPart != null && !absPart.equals("")) { + pixels = convertLength(absPart); + } + + absTotal += pixels; + absParts[count] = pixels; + } else { + relParts[count] = 0; + + int pixels = 0; + if (width != null && !width.equals("")) { + pixels = convertLength(width); + } + + absTotal += pixels; + absParts[count] = pixels; + } + } + + // Ok, now we have the relative widths and absolute widths in + // two parallel arrays. + // + // - If there are no relative widths, output the absolute widths + // - If there are no absolute widths, output the relative widths + // - If there are a mixture of relative and absolute widths, + // - If the table width is absolute, turn these all into absolute + // widths. + // - If the table width is relative, turn these all into absolute + // widths in the nominalWidth and then turn them back into + // percentages. + + if (relTotal == 0) { + for (int count = 0; count < numColumns; count++) { + Float f = new Float(absParts[count]); + if (foStylesheet) { + int pixels = f.intValue(); + float inches = (float) pixels / pixelsPerInch; + widths[count] = inches + "in"; + } else { + widths[count] = Integer.toString(f.intValue()); + } + } + } else if (absTotal == 0) { + for (int count = 0; count < numColumns; count++) { + float rel = relParts[count] / relTotal * 100; + Float f = new Float(rel); + widths[count] = Integer.toString(f.intValue()); + } + widths = correctRoundingError(widths); + } else { + int pixelWidth = nominalWidth; + + if (tableWidth.indexOf("%") <= 0) { + pixelWidth = convertLength(tableWidth); + } + + if (pixelWidth <= absTotal) { + System.out.println("Table is wider than table width."); + } else { + pixelWidth -= absTotal; + } + + absTotal = 0; + for (int count = 0; count < numColumns; count++) { + float rel = relParts[count] / relTotal * pixelWidth; + relParts[count] = rel + absParts[count]; + absTotal += rel + absParts[count]; + } + + if (tableWidth.indexOf("%") <= 0) { + for (int count = 0; count < numColumns; count++) { + Float f = new Float(relParts[count]); + if (foStylesheet) { + int pixels = f.intValue(); + float inches = (float) pixels / pixelsPerInch; + widths[count] = inches + "in"; + } else { + widths[count] = Integer.toString(f.intValue()); + } + } + } else { + for (int count = 0; count < numColumns; count++) { + float rel = relParts[count] / absTotal * 100; + Float f = new Float(rel); + widths[count] = Integer.toString(f.intValue()); + } + widths = correctRoundingError(widths); + } + } + + // Now rebuild the colgroup with the right widths + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = null; + + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + System.out.println("PCE!"); + return xalanRTF; + } + Document doc = docBuilder.newDocument(); + DocumentFragment df = doc.createDocumentFragment(); + DOMBuilder rtf = new DOMBuilder(doc, df); + + try { + String ns = colgroup.getNamespaceURI(); + String localName = colgroup.getLocalName(); + String name = colgroup.getTagName(); + + if (colgroup.getLocalName().equals("colgroup")) { + rtf.startElement(ns, localName, name, + copyAttributes(colgroup)); + } + + for (colnum = 0; colnum < numColumns; colnum++) { + Element col = columns[colnum]; + + NamedNodeMap domAttr = col.getAttributes(); + + AttributesImpl attr = new AttributesImpl(); + for (int acount = 0; acount < domAttr.getLength(); acount++) { + Node a = domAttr.item(acount); + String a_ns = a.getNamespaceURI(); + String a_localName = a.getLocalName(); + + if ((foStylesheet && !a_localName.equals("column-width")) + || !a_localName.equalsIgnoreCase("width")) { + attr.addAttribute(a.getNamespaceURI(), + a.getLocalName(), + a.getNodeName(), + "CDATA", + a.getNodeValue()); + } + } + + if (foStylesheet) { + attr.addAttribute("", "column-width", "column-width", "CDATA", widths[colnum]); + } else { + attr.addAttribute("", "width", "width", "CDATA", widths[colnum]); + } + + rtf.startElement(col.getNamespaceURI(), + col.getLocalName(), + col.getTagName(), + attr); + rtf.endElement(col.getNamespaceURI(), + col.getLocalName(), + col.getTagName()); + } + + if (colgroup.getLocalName().equals("colgroup")) { + rtf.endElement(ns, localName, name); + } + } catch (SAXException se) { + System.out.println("SE!"); + return xalanRTF; + } + + return df; + } + + private Attributes copyAttributes(Element node) { + AttributesImpl attrs = new AttributesImpl(); + NamedNodeMap nnm = node.getAttributes(); + for (int count = 0; count < nnm.getLength(); count++) { + Attr attr = (Attr) nnm.item(count); + String name = attr.getName(); + if (name.startsWith("xmlns:") || name.equals("xmlns")) { + // Skip it; (don't ya just love it!!) + } else { + attrs.addAttribute(attr.getNamespaceURI(), attr.getName(), + attr.getName(), "CDATA", attr.getValue()); + } + } + return attrs; + } + + /** + * Correct rounding errors introduced in calculating the width of each + * column. Make sure they sum to 100% in the end. + */ + protected String[] correctRoundingError(String widths[]) { + int totalWidth = 0; + + for (int count = 0; count < widths.length; count++) { + try { + int width = Integer.parseInt(widths[count]); + totalWidth += width; + } catch (NumberFormatException nfe) { + // nop; "can't happen" + } + } + + float totalError = 100 - totalWidth; + float columnError = totalError / widths.length; + float error = 0; + + for (int count = 0; count < widths.length; count++) { + try { + int width = Integer.parseInt(widths[count]); + error = error + columnError; + if (error >= 1.0) { + int adj = (int) Math.round(Math.floor(error)); + error = error - (float) Math.floor(error); + width = width + adj; + widths[count] = Integer.toString(width) + "%"; + } else { + widths[count] = Integer.toString(width) + "%"; + } + } catch (NumberFormatException nfe) { + // nop; "can't happen" + } + } + + return widths; + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Text.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Text.java new file mode 100644 index 000000000..0baa2c9af --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Text.java @@ -0,0 +1,205 @@ +// Text - Xalan extension element for inserting text + +package com.nwalsh.xalan; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.InputStream; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.net.URL; +import java.net.MalformedURLException; + +import org.xml.sax.SAXException; +import org.xml.sax.AttributeList; +import org.xml.sax.ContentHandler; + +import org.w3c.dom.*; +import org.apache.xerces.dom.*; + +import org.apache.xpath.objects.XObject; +import org.apache.xpath.XPath; +import org.apache.xpath.NodeSet; +import org.apache.xalan.extensions.XSLProcessorContext; +import org.apache.xalan.transformer.TransformerImpl; +import org.apache.xalan.templates.StylesheetRoot; +import org.apache.xalan.templates.ElemExtensionCall; +import org.apache.xalan.templates.OutputProperties; +import org.apache.xalan.res.XSLTErrorResources; + +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.TransformerException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.Source; + +/** + *

Xalan extension element for inserting text + * + *

$Id$

+ * + *

Copyright (C) 2001 Norman Walsh.

+ * + *

This class provides a + * Xalan + * extension element for inserting text into a result tree.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class Text { + /** + *

Constructor for Text

+ * + *

Does nothing.

+ */ + public Text() { + } + + public String insertfile(XSLProcessorContext context, + ElemExtensionCall elem) + throws MalformedURLException, + FileNotFoundException, + IOException, + TransformerException { + String href = getFilename(context, elem); + String encoding = getEncoding(context, elem); + + String baseURI = context.getTransformer().getBaseURLOfSource(); + URIResolver resolver = context.getTransformer().getURIResolver(); + + if (resolver != null) { + Source source = resolver.resolve(href, baseURI); + href = source.getSystemId(); + } + + URL baseURL = null; + URL fileURL = null; + + try { + baseURL = new URL(baseURI); + } catch (MalformedURLException e1) { + try { + baseURL = new URL("file:" + baseURI); + } catch (MalformedURLException e2) { + System.out.println("Cannot find base URI for " + baseURI); + baseURL = null; + } + } + + String text = ""; + + try { + try { + fileURL = new URL(baseURL, href); + } catch (MalformedURLException e1) { + try { + fileURL = new URL(baseURL, "file:" + href); + } catch (MalformedURLException e2) { + System.out.println("Cannot open " + href); + return ""; + } + } + + InputStreamReader isr = null; + if (encoding.equals("") == true) + isr = new InputStreamReader(fileURL.openStream()); + else + isr = new InputStreamReader(fileURL.openStream(), encoding); + + BufferedReader is = new BufferedReader(isr); + + final int BUFFER_SIZE = 4096; + char chars[] = new char[BUFFER_SIZE]; + char nchars[] = new char[BUFFER_SIZE]; + int len = 0; + int i = 0; + int carry = -1; + + while ((len = is.read(chars)) > 0) { + // various new lines are normalized to LF to prevent blank lines + // between lines + + int nlen = 0; + for (i=0; i LF to normalize MAC line endings + nchars[nlen] = '\n'; + nlen++; + continue; + } else { + // if CR is last char of buffer we must look ahead + carry = is.read(); + nchars[nlen] = '\n'; + nlen++; + if (carry == '\n') { + carry = -1; + } + break; + } + } + nchars[nlen] = chars[i]; + nlen++; + } + + text += String.valueOf(nchars, 0, nlen); + + // handle look aheaded character + if (carry != -1) text += String.valueOf((char)carry); + carry = -1; + } + is.close(); + } catch (Exception e) { + System.out.println("Cannot read " + href); + } + + return text; + } + + private String getFilename(XSLProcessorContext context, ElemExtensionCall elem) + throws java.net.MalformedURLException, + java.io.FileNotFoundException, + java.io.IOException, + javax.xml.transform.TransformerException { + + String fileName; + + fileName = ((ElemExtensionCall)elem).getAttribute ("href", + context.getContextNode(), + context.getTransformer()); + + if ("".equals(fileName)) { + context.getTransformer().getMsgMgr().error(elem, + "No 'href' on text, or not a filename"); + } + + return fileName; + } + + private String getEncoding(XSLProcessorContext context, ElemExtensionCall elem) + throws java.net.MalformedURLException, + java.io.FileNotFoundException, + java.io.IOException, + javax.xml.transform.TransformerException { + + String encoding; + + encoding = ((ElemExtensionCall)elem).getAttribute ("encoding", + context.getContextNode(), + context.getTransformer()); + + return encoding; + } +} diff --git a/xsl/extensions/xalan27/src/com/nwalsh/xalan/Verbatim.java b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Verbatim.java new file mode 100644 index 000000000..3799f1126 --- /dev/null +++ b/xsl/extensions/xalan27/src/com/nwalsh/xalan/Verbatim.java @@ -0,0 +1,926 @@ +// Verbatim.java - Xalan extensions supporting DocBook verbatim environments + +package com.nwalsh.xalan; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.apache.xml.utils.DOMHelper; + +import org.xml.sax.*; +import org.xml.sax.helpers.AttributesImpl; +import org.w3c.dom.*; +import org.w3c.dom.traversal.NodeIterator; +import org.apache.xerces.dom.*; + +import org.apache.xpath.objects.XObject; +import org.apache.xpath.XPath; +import org.apache.xpath.XPathContext; +import org.apache.xpath.NodeSet; +import org.apache.xalan.extensions.XSLProcessorContext; +import org.apache.xalan.extensions.ExpressionContext; +import org.apache.xalan.transformer.TransformerImpl; +import org.apache.xalan.templates.StylesheetRoot; +import org.apache.xalan.templates.ElemExtensionCall; +import org.apache.xalan.templates.OutputProperties; +import org.apache.xalan.res.XSLTErrorResources; +import org.apache.xml.utils.DOMBuilder; +import org.apache.xml.utils.AttList; +import org.apache.xml.utils.QName; + +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.TransformerException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import com.nwalsh.xalan.Callout; +import com.nwalsh.xalan.Params; + +/** + *

Xalan extensions supporting DocBook verbatim environments

+ * + *

$Id$

+ * + *

Copyright (C) 2001 Norman Walsh.

+ * + *

This class provides a + * Xalan + * implementation of two features that would be impractical to + * implement directly in XSLT: line numbering and callouts.

+ * + *

Line Numbering

+ *

The numberLines family of functions takes a result tree + * fragment (assumed to contain the contents of a formatted verbatim + * element in DocBook: programlisting, screen, address, literallayout, + * or synopsis) and returns a result tree fragment decorated with + * line numbers.

+ * + *

Callouts

+ *

The insertCallouts family of functions takes an + * areaspec and a result tree fragment + * (assumed to contain the contents of a formatted verbatim + * element in DocBook: programlisting, screen, address, literallayout, + * or synopsis) and returns a result tree fragment decorated with + * callouts.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class Verbatim { + /** A stack to hold the open elements while walking through a RTF. */ + private Stack elementStack = null; + /** A stack to hold the temporarily closed elements. */ + private Stack tempStack = null; + /** The current line number. */ + private int lineNumber = 0; + /** The current column number. */ + private int colNumber = 0; + /** The modulus for line numbering (every 'modulus' line is numbered). */ + private int modulus = 0; + /** The width (in characters) of line numbers (for padding). */ + private int width = 0; + /** The separator between the line number and the verbatim text. */ + private String separator = ""; + /** The (sorted) array of callouts obtained from the areaspec. */ + private Callout callout[] = null; + /** The number of callouts in the callout array. */ + private int calloutCount = 0; + /** A pointer used to keep track of our position in the callout array. */ + private int calloutPos = 0; + /** The path to use for graphical callout decorations. */ + private String graphicsPath = null; + /** The extension to use for graphical callout decorations. */ + private String graphicsExt = null; + /** The largest callout number that can be represented graphically. */ + private int graphicsMax = 10; + /** Should graphic callouts use fo:external-graphics or imgs. */ + private boolean graphicsFO = false; + + private static final String foURI = "http://www.w3.org/1999/XSL/Format"; + private static final String xhURI = "http://www.w3.org/1999/xhtml"; + + /** + *

Constructor for Verbatim

+ * + *

All of the methods are static, so the constructor does nothing.

+ */ + public Verbatim() { + } + + /** + *

Number lines in a verbatim environment.

+ * + *

This method adds line numbers to a result tree fragment. Each + * newline that occurs in a text node is assumed to start a new line. + * The first line is always numbered, every subsequent xalanMod line + * is numbered (so if xalanMod=5, lines 1, 5, 10, 15, etc. will be + * numbered. If there are fewer than xalanMod lines in the environment, + * every line is numbered.

+ * + *

xalanMod is taken from the $linenumbering.everyNth parameter.

+ * + *

Every line number will be right justified in a string xalanWidth + * characters long. If the line number of the last line in the + * environment is too long to fit in the specified width, the width + * is automatically increased to the smallest value that can hold the + * number of the last line. (In other words, if you specify the value 2 + * and attempt to enumerate the lines of an environment that is 100 lines + * long, the value 3 will automatically be used for every line in the + * environment.)

+ * + *

xalanWidth is taken from the $linenumbering.width parameter.

+ * + *

The xalanSep string is inserted between the line + * number and the original program listing. Lines that aren't numbered + * are preceded by a xalanWidth blank string and the separator.

+ * + *

xalanSep is taken from the $linenumbering.separator parameter.

+ * + *

If inline markup extends across line breaks, markup changes are + * required. All the open elements are closed before the line break and + * "reopened" afterwards. The reopened elements will have the same + * attributes as the originals, except that 'name' and 'id' attributes + * are not duplicated.

+ * + * @param context + * @param xalanNI + * + * @return The modified result tree fragment. + */ + public DocumentFragment numberLines (ExpressionContext context, + NodeIterator xalanNI) { + + int xalanMod = Params.getInt(context, "linenumbering.everyNth"); + int xalanWidth = Params.getInt(context, "linenumbering.width"); + String xalanSep = Params.getString(context, "linenumbering.separator"); + + DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode(); + int numLines = countLineBreaks(xalanRTF) + 1; + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = null; + + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + System.out.println("PCE!"); + return xalanRTF; + } + Document doc = docBuilder.newDocument(); + DocumentFragment df = doc.createDocumentFragment(); + DOMBuilder db = new DOMBuilder(doc, df); + + elementStack = new Stack(); + lineNumber = 0; + modulus = numLines < xalanMod ? 1 : xalanMod; + width = xalanWidth; + separator = xalanSep; + + double log10numLines = Math.log(numLines) / Math.log(10); + + if (width < log10numLines + 1) { + width = (int) Math.floor(log10numLines + 1); + } + + lineNumberFragment(db, xalanRTF); + return df; + } + + /** + *

Count the number of lines in a verbatim environment.

+ * + *

This method walks over the nodes of a DocumentFragment and + * returns the number of lines breaks that it contains.

+ * + * @param node The root of the tree walk over. + */ + private int countLineBreaks(Node node) { + int numLines = 0; + + if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE + || node.getNodeType() == Node.DOCUMENT_NODE + || node.getNodeType() == Node.ELEMENT_NODE) { + Node child = node.getFirstChild(); + while (child != null) { + numLines += countLineBreaks(child); + child = child.getNextSibling(); + } + } else if (node.getNodeType() == Node.TEXT_NODE) { + String text = node.getNodeValue(); + + // Walk through the text node looking for newlines + int pos = 0; + for (int count = 0; count < text.length(); count++) { + if (text.charAt(count) == '\n') { + numLines++; + } + } + } else { + // nop + } + + return numLines; + } + + /** + *

Build a DocumentFragment with numbered lines.

+ * + *

This is the method that actually does the work of numbering + * lines in a verbatim environment. It recursively walks through a + * tree of nodes, copying the structure into the rtf. Text nodes + * are examined for new lines and modified as requested by the + * global line numbering parameters.

+ * + *

When called, rtf should be an empty DocumentFragment and node + * should be the first child of the result tree fragment that contains + * the existing, formatted verbatim text.

+ * + * @param rtf The resulting verbatim environment with numbered lines. + * @param node The root of the tree to copy. + */ + private void lineNumberFragment(DOMBuilder rtf, + Node node) { + try { + if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE + || node.getNodeType() == Node.DOCUMENT_NODE) { + Node child = node.getFirstChild(); + while (child != null) { + lineNumberFragment(rtf, child); + child = child.getNextSibling(); + } + } else if (node.getNodeType() == Node.ELEMENT_NODE) { + String ns = node.getNamespaceURI(); + String localName = node.getLocalName(); + String name = ((Element) node).getTagName(); + + rtf.startElement(ns, localName, name, + copyAttributes((Element) node)); + + elementStack.push(node); + + Node child = node.getFirstChild(); + while (child != null) { + lineNumberFragment(rtf, child); + child = child.getNextSibling(); + } + } else if (node.getNodeType() == Node.TEXT_NODE) { + String text = node.getNodeValue(); + + if (lineNumber == 0) { + // The first line is always numbered + formatLineNumber(rtf, ++lineNumber); + } + + // Walk through the text node looking for newlines + char chars[] = text.toCharArray(); + int pos = 0; + for (int count = 0; count < text.length(); count++) { + if (text.charAt(count) == '\n') { + // This is the tricky bit; if we find a newline, make sure + // it doesn't occur inside any markup. + + if (pos > 0) { + rtf.characters(chars, 0, pos); + pos = 0; + } + + closeOpenElements(rtf); + + // Copy the newline to the output + chars[pos++] = text.charAt(count); + rtf.characters(chars, 0, pos); + pos = 0; + + // Add the line number + formatLineNumber(rtf, ++lineNumber); + + openClosedElements(rtf); + } else { + chars[pos++] = text.charAt(count); + } + } + + if (pos > 0) { + rtf.characters(chars, 0, pos); + } + } else if (node.getNodeType() == Node.COMMENT_NODE) { + String text = node.getNodeValue(); + char chars[] = text.toCharArray(); + rtf.comment(chars, 0, text.length()); + } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { + rtf.processingInstruction(node.getNodeName(), node.getNodeValue()); + } else { + System.out.println("Warning: unexpected node type in lineNumberFragment"); + } + + if (node.getNodeType() == Node.ELEMENT_NODE) { + String ns = node.getNamespaceURI(); + String localName = node.getLocalName(); + String name = ((Element) node).getTagName(); + rtf.endElement(ns, localName, name); + elementStack.pop(); + } + } catch (SAXException e) { + System.out.println("SAX Exception in lineNumberFragment"); + } + } + + /** + *

Add a formatted line number to the result tree fragment.

+ * + *

This method examines the global parameters that control line + * number presentation (modulus, width, and separator) and adds + * the appropriate text to the result tree fragment.

+ * + * @param rtf The resulting verbatim environment with numbered lines. + * @param lineNumber The number of the current line. + */ + private void formatLineNumber(DOMBuilder rtf, + int lineNumber) { + char ch = 160; + String lno = ""; + if (lineNumber == 1 + || (modulus >= 1 && (lineNumber % modulus == 0))) { + lno = "" + lineNumber; + } + + while (lno.length() < width) { + lno = ch + lno; + } + + lno += separator; + + char chars[] = lno.toCharArray(); + try { + rtf.characters(chars, 0, lno.length()); + } catch (SAXException e) { + System.out.println("SAX Exception in formatLineNumber"); + } + } + + /** + *

Insert text callouts into a verbatim environment.

+ * + *

This method examines the areaset and area elements + * in the supplied areaspec and decorates the supplied + * result tree fragment with appropriate callout markers.

+ * + *

If a label attribute is supplied on an area, + * its content will be used for the label, otherwise the callout + * number will be used, surrounded by parenthesis. Callouts are + * numbered in document order. All of the areas in an + * areaset get the same number.

+ * + *

Only the linecolumn and linerange units are + * supported. If no unit is specifed, linecolumn is assumed. + * If only a line is specified, the callout decoration appears in + * the defaultColumn. Lines will be padded with blanks to reach the + * necessary column, but callouts that are located beyond the last + * line of the verbatim environment will be ignored.

+ * + *

Callouts are inserted before the character at the line/column + * where they are to occur.

+ * + * @param areaspecNodeSet The source node set that contains the areaspec. + * @param xalanRTF The result tree fragment of the verbatim environment. + * @param defaultColumn The column for callouts that specify only a line. + * + * @return The modified result tree fragment. */ + + /** + *

Insert graphical callouts into a verbatim environment.

+ * + *

This method examines the areaset and area elements + * in the supplied areaspec and decorates the supplied + * result tree fragment with appropriate callout markers.

+ * + *

If a label attribute is supplied on an area, + * its content will be used for the label, otherwise the callout + * number will be used. Callouts are + * numbered in document order. All of the areas in an + * areaset get the same number.

+ * + *

If the callout number is not greater than gMax, the + * callout generated will be:

+ * + *
+   * <img src="$gPath/conumber$gExt" alt="conumber">
+   * 
+ * + *

Otherwise, it will be the callout number surrounded by + * parenthesis.

+ * + *

Only the linecolumn and linerange units are + * supported. If no unit is specifed, linecolumn is assumed. + * If only a line is specified, the callout decoration appears in + * the defaultColumn. Lines will be padded with blanks to reach the + * necessary column, but callouts that are located beyond the last + * line of the verbatim environment will be ignored.

+ * + *

Callouts are inserted before the character at the line/column + * where they are to occur.

+ * + * @param context + * @param areaspecNodeSet The source node set that contains the areaspec. + * @param xalanNI + * + * @return The modified result tree fragment. + */ + public DocumentFragment insertCallouts (ExpressionContext context, + NodeIterator areaspecNodeSet, + NodeIterator xalanNI) { + String type = Params.getString(context, "stylesheet.result.type"); + boolean useFO = type.equals("fo"); + int defaultColumn = Params.getInt(context, "callout.defaultcolumn"); + + if (Params.getBoolean(context, "callout.graphics")) { + String gPath = Params.getString(context, "callout.graphics.path"); + String gExt = Params.getString(context, "callout.graphics.extension"); + int gMax = Params.getInt(context, "callout.graphics.number.limit"); + return insertGraphicCallouts(areaspecNodeSet, xalanNI, defaultColumn, + gPath, gExt, gMax, useFO); + } else if (Params.getBoolean(context, "callout.unicode")) { + int uStart = Params.getInt(context, "callout.unicode.start.character"); + int uMax = Params.getInt(context, "callout.unicode.number.limit"); + String uFont = Params.getString(context, "callout.unicode.font"); + return insertUnicodeCallouts(areaspecNodeSet, xalanNI, defaultColumn, + uFont, uStart, uMax, useFO); + } else if (Params.getBoolean(context, "callout.dingbats")) { + int dMax = 10; + return insertDingbatCallouts(areaspecNodeSet, xalanNI, defaultColumn, + dMax, useFO); + } else { + return insertTextCallouts(areaspecNodeSet, xalanNI, defaultColumn, useFO); + } + } + + public DocumentFragment insertGraphicCallouts (NodeIterator areaspecNodeSet, + NodeIterator xalanNI, + int defaultColumn, + String gPath, + String gExt, + int gMax, + boolean useFO) { + FormatGraphicCallout fgc = new FormatGraphicCallout(gPath,gExt,gMax,useFO); + return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fgc); + } + + public DocumentFragment insertUnicodeCallouts (NodeIterator areaspecNodeSet, + NodeIterator xalanNI, + int defaultColumn, + String uFont, + int uStart, + int uMax, + boolean useFO) { + FormatUnicodeCallout fuc = new FormatUnicodeCallout(uFont, uStart, uMax, useFO); + return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fuc); + } + + public DocumentFragment insertDingbatCallouts (NodeIterator areaspecNodeSet, + NodeIterator xalanNI, + int defaultColumn, + int gMax, + boolean useFO) { + FormatDingbatCallout fdc = new FormatDingbatCallout(gMax,useFO); + return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, fdc); + } + + public DocumentFragment insertTextCallouts (NodeIterator areaspecNodeSet, + NodeIterator xalanNI, + int defaultColumn, + boolean useFO) { + FormatTextCallout ftc = new FormatTextCallout(useFO); + return insertCallouts(areaspecNodeSet, xalanNI, defaultColumn, ftc); + } + + public DocumentFragment insertCallouts (NodeIterator areaspecNodeSet, + NodeIterator xalanNI, + int defaultColumn, + FormatCallout fCallout) { + + DocumentFragment xalanRTF = (DocumentFragment) xalanNI.nextNode(); + + callout = new Callout[10]; + calloutCount = 0; + calloutPos = 0; + lineNumber = 1; + colNumber = 1; + + // First we walk through the areaspec to calculate the position + // of the callouts + // + // + // + // + // + // + // + // + int pos = 0; + int coNum = 0; + boolean inAreaSet = false; + Node node = areaspecNodeSet.nextNode(); + node = node.getFirstChild(); + while (node != null) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (node.getNodeName().equals("areaset")) { + coNum++; + Node area = node.getFirstChild(); + while (area != null) { + if (area.getNodeType() == Node.ELEMENT_NODE) { + if (area.getNodeName().equals("area")) { + addCallout(coNum, area, defaultColumn); + } else { + System.out.println("Unexpected element in areaset: " + + area.getNodeName()); + } + } + area = area.getNextSibling(); + } + } else if (node.getNodeName().equalsIgnoreCase("area")) { + coNum++; + addCallout(coNum, node, defaultColumn); + } else { + System.out.println("Unexpected element in areaspec: " + + node.getNodeName()); + } + } + + node = node.getNextSibling(); + } + + // Now sort them + java.util.Arrays.sort(callout, 0, calloutCount); + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = null; + + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + System.out.println("PCE 2!"); + return xalanRTF; + } + Document doc = docBuilder.newDocument(); + DocumentFragment df = doc.createDocumentFragment(); + DOMBuilder db = new DOMBuilder(doc, df); + + elementStack = new Stack(); + calloutFragment(db, xalanRTF, fCallout); + return df; + } + + /** + *

Build a FragmentValue with callout decorations.

+ * + *

This is the method that actually does the work of adding + * callouts to a verbatim environment. It recursively walks through a + * tree of nodes, copying the structure into the rtf. Text nodes + * are examined for the position of callouts as described by the + * global callout parameters.

+ * + *

When called, rtf should be an empty FragmentValue and node + * should be the first child of the result tree fragment that contains + * the existing, formatted verbatim text.

+ * + * @param rtf The resulting verbatim environment with numbered lines. + * @param node The root of the tree to copy. + */ + private void calloutFragment(DOMBuilder rtf, + Node node, + FormatCallout fCallout) { + try { + if (node.getNodeType() == Node.DOCUMENT_FRAGMENT_NODE + || node.getNodeType() == Node.DOCUMENT_NODE) { + Node child = node.getFirstChild(); + while (child != null) { + calloutFragment(rtf, child, fCallout); + child = child.getNextSibling(); + } + } else if (node.getNodeType() == Node.ELEMENT_NODE) { + String ns = node.getNamespaceURI(); + String localName = node.getLocalName(); + String name = ((Element) node).getTagName(); + + rtf.startElement(ns, localName, name, + copyAttributes((Element) node)); + + elementStack.push(node); + + Node child = node.getFirstChild(); + while (child != null) { + calloutFragment(rtf, child, fCallout); + child = child.getNextSibling(); + } + } else if (node.getNodeType() == Node.TEXT_NODE) { + String text = node.getNodeValue(); + + char chars[] = text.toCharArray(); + int pos = 0; + for (int count = 0; count < text.length(); count++) { + if (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + + if (pos > 0) { + rtf.characters(chars, 0, pos); + pos = 0; + } + + closeOpenElements(rtf); + + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + fCallout.formatCallout(rtf, callout[calloutPos]); + calloutPos++; + } + + openClosedElements(rtf); + } + + if (text.charAt(count) == '\n') { + // What if we need to pad this line? + if (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() > colNumber) { + + if (pos > 0) { + rtf.characters(chars, 0, pos); + pos = 0; + } + + closeOpenElements(rtf); + + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() > colNumber) { + formatPad(rtf, callout[calloutPos].getColumn() - colNumber); + colNumber = callout[calloutPos].getColumn(); + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + fCallout.formatCallout(rtf, callout[calloutPos]); + calloutPos++; + } + } + + openClosedElements(rtf); + } + + lineNumber++; + colNumber = 1; + } else { + colNumber++; + } + chars[pos++] = text.charAt(count); + } + + if (pos > 0) { + rtf.characters(chars, 0, pos); + } + } else if (node.getNodeType() == Node.COMMENT_NODE) { + String text = node.getNodeValue(); + char chars[] = text.toCharArray(); + rtf.comment(chars, 0, text.length()); + } else if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) { + rtf.processingInstruction(node.getNodeName(), node.getNodeValue()); + } else { + System.out.println("Warning: unexpected node type in calloutFragment: " + node.getNodeType() + ": " + node.getNodeName()); + } + + if (node.getNodeType() == Node.ELEMENT_NODE) { + String ns = node.getNamespaceURI(); + String localName = node.getLocalName(); + String name = ((Element) node).getTagName(); + rtf.endElement(ns, localName, name); + elementStack.pop(); + } else { + // nop + } + } catch (SAXException e) { + System.out.println("SAX Exception in calloutFragment"); + } + } + + /** + *

Add a callout to the global callout array

+ * + *

This method examines a callout area and adds it to + * the global callout array if it can be interpreted.

+ * + *

Only the linecolumn and linerange units are + * supported. If no unit is specifed, linecolumn is assumed. + * If only a line is specified, the callout decoration appears in + * the defaultColumn.

+ * + * @param coNum The callout number. + * @param node The area. + * @param defaultColumn The default column for callouts. + */ + private void addCallout (int coNum, + Node node, + int defaultColumn) { + Element area = (Element) node; + + String units = area.getAttribute("units"); + String otherUnits = area.getAttribute("otherunits"); + String coords = area.getAttribute("coords"); + int type = 0; + String otherType = null; + + if ("".equals(units) || units.equals("linecolumn")) { + type = Callout.LINE_COLUMN; // the default + } else if (units.equals("linerange")) { + type = Callout.LINE_RANGE; + } else if (units.equals("linecolumnpair")) { + type = Callout.LINE_COLUMN_PAIR; + } else if (units.equals("calspair")) { + type = Callout.CALS_PAIR; + } else { + type = Callout.OTHER; + otherType = otherUnits; + } + + if (type != Callout.LINE_COLUMN + && type != Callout.LINE_RANGE) { + System.out.println("Only linecolumn and linerange units are supported"); + return; + } + + if (coords == null) { + System.out.println("Coords must be specified"); + return; + } + + // Now let's see if we can interpret the coordinates... + StringTokenizer st = new StringTokenizer(coords); + int tokenCount = 0; + int c1 = 0; + int c2 = 0; + while (st.hasMoreTokens()) { + tokenCount++; + if (tokenCount > 2) { + System.out.println("Unparseable coordinates"); + return; + } + try { + String token = st.nextToken(); + int coord = Integer.parseInt(token); + c2 = coord; + if (tokenCount == 1) { + c1 = coord; + } + } catch (NumberFormatException e) { + System.out.println("Unparseable coordinate"); + return; + } + } + + // Make sure we aren't going to blow past the end of our array + if (calloutCount == callout.length) { + Callout bigger[] = new Callout[calloutCount+10]; + for (int count = 0; count < callout.length; count++) { + bigger[count] = callout[count]; + } + callout = bigger; + } + + // Ok, add the callout + if (tokenCount == 2) { + if (type == Callout.LINE_RANGE) { + for (int count = c1; count <= c2; count++) { + callout[calloutCount++] = new Callout(coNum, area, + count, defaultColumn, + type); + } + } else { + // assume linecolumn + callout[calloutCount++] = new Callout(coNum, area, c1, c2, type); + } + } else { + // if there's only one number, assume it's the line + callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn, type); + } + } + + /** + *

Add blanks to the result tree fragment.

+ * + *

This method adds numBlanks to the result tree fragment. + * It's used to pad lines when callouts occur after the last existing + * characater in a line.

+ * + * @param rtf The resulting verbatim environment with numbered lines. + * @param numBlanks The number of blanks to add. + */ + private void formatPad(DOMBuilder rtf, + int numBlanks) { + char chars[] = new char[numBlanks]; + for (int count = 0; count < numBlanks; count++) { + chars[count] = ' '; + } + + try { + rtf.characters(chars, 0, numBlanks); + } catch (SAXException e) { + System.out.println("SAX Exception in formatCallout"); + } + } + + private void closeOpenElements(DOMBuilder rtf) + throws SAXException { + // Close all the open elements... + tempStack = new Stack(); + while (!elementStack.empty()) { + Node elem = (Node) elementStack.pop(); + + String ns = elem.getNamespaceURI(); + String localName = elem.getLocalName(); + String name = ((Element) elem).getTagName(); + + // If this is the bottom of the stack and it's an fo:block + // or an HTML pre or div, don't duplicate it... + if (elementStack.empty() + && (((ns != null) + && ns.equals(foURI) + && localName.equals("block")) + || (((ns == null) + && localName.equalsIgnoreCase("pre")) + || ((ns != null) + && ns.equals(xhURI) + && localName.equals("pre"))) + || (((ns == null) + && localName.equalsIgnoreCase("div")) + || ((ns != null) + && ns.equals(xhURI) + && localName.equals("div"))))) { + elementStack.push(elem); + break; + } else { + rtf.endElement(ns, localName, name); + tempStack.push(elem); + } + } + } + + private void openClosedElements(DOMBuilder rtf) + throws SAXException { + // Now "reopen" the elements that we closed... + while (!tempStack.empty()) { + Node elem = (Node) tempStack.pop(); + + String ns = elem.getNamespaceURI(); + String localName = elem.getLocalName(); + String name = ((Element) elem).getTagName(); + NamedNodeMap domAttr = elem.getAttributes(); + + AttributesImpl attr = new AttributesImpl(); + for (int acount = 0; acount < domAttr.getLength(); acount++) { + Node a = domAttr.item(acount); + + if (((ns == null || ns == "http://www.w3.org/1999/xhtml") + && localName.equalsIgnoreCase("a")) + || (a.getLocalName().equalsIgnoreCase("id"))) { + // skip this attribute + } else { + attr.addAttribute(a.getNamespaceURI(), + a.getLocalName(), + a.getNodeName(), + "CDATA", + a.getNodeValue()); + } + } + + rtf.startElement(ns, localName, name, attr); + elementStack.push(elem); + } + + tempStack = null; + } + + private Attributes copyAttributes(Element node) { + AttributesImpl attrs = new AttributesImpl(); + NamedNodeMap nnm = node.getAttributes(); + for (int count = 0; count < nnm.getLength(); count++) { + Attr attr = (Attr) nnm.item(count); + String name = attr.getName(); + if (name.startsWith("xmlns:") || name.equals("xmlns")) { + // Skip it; (don't ya just love it!!) + } else { + attrs.addAttribute(attr.getNamespaceURI(), attr.getName(), + attr.getName(), "CDATA", attr.getValue()); + } + } + return attrs; + } +} -- 2.40.0