From: Michael Smith Date: Mon, 5 Mar 2007 17:01:58 +0000 (+0000) Subject: Copied xsl/extensions up a level to create a top-level X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=9807f8a505e2f1ab26d94aa4f8221574b806571f;p=docbook-dsssl Copied xsl/extensions up a level to create a top-level xsl-java module. Rationale is that we need to do a separate docbook-xsl-java release in order to get the XSL Java extensions packaged for Debian, and having it as a top-level module allows the release build and packaging to be done using the common build files in releasetools/ and so makes it a lot easier to set up and maintain the build and do the packaging and upload, etc. After OK, would be best to next remove the xsl/extensions directory from version control and instead update the docbook-xsl build to simply copy over the built jars from the xsl-java dir. --- diff --git a/xsl-java/AUTHORS b/xsl-java/AUTHORS new file mode 100644 index 000000000..5fba4b5fb --- /dev/null +++ b/xsl-java/AUTHORS @@ -0,0 +1,3 @@ +The DocBook XSL Java extensions are maintained by Norman Walsh, +, and members of the DocBook Project, + diff --git a/xsl-java/BUGS b/xsl-java/BUGS new file mode 100644 index 000000000..a8152cfd3 --- /dev/null +++ b/xsl-java/BUGS @@ -0,0 +1,23 @@ +To view a list of all open bugs for the DocBook Project XSL Java +extensions: + + http://docbook.sf.net/tracker/xsl-java/bugs + +To submit a bug report against the XSL Java extensions:: + + http://docbook.sf.net/tracker/submit/bug + +To do a full-text search of all DocBook Project issues: + + http://docbook.sf.net/tracker/search + +The XSL Java extensions are for use with the DocBook Project XSL +stylesheets. Discussion about the DocBook XSL stylesheets takes +place on the docbook-apps mailing list: + + http://wiki.docbook.org/topic/DocBookAppsMailingList + +Real-time discussion takes place on IRC: + + http://wiki.docbook.org/topic/DocBookIrcChannel + irc://irc.freenode.net/docbook diff --git a/xsl-java/COPYING b/xsl-java/COPYING new file mode 100644 index 000000000..555b97f51 --- /dev/null +++ b/xsl-java/COPYING @@ -0,0 +1,45 @@ +Copyright +--------- +Copyright (C) 1999-2007 Norman Walsh +Copyright (C) 2005-2007 The DocBook Project + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the ``Software''), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +Except as contained in this notice, the names of individuals +credited with contribution to this software shall not be used in +advertising or otherwise to promote the sale, use or other +dealings in this Software without prior written authorization +from the individuals in question. + +Any stylesheet derived from this Software that is publically +distributed will be identified with a different name and the +version strings in any derived Software will be changed so that +no possibility of confusion between the derived package and this +Software will exist. + +Warranty +-------- +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL NORMAN WALSH OR ANY OTHER +CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +Contacting the Author +--------------------- +The DocBook XSL Java extensions are maintained by Norman Walsh, +, and members of the DocBook Project, + diff --git a/xsl-java/INSTALL b/xsl-java/INSTALL new file mode 100644 index 000000000..37081adf5 --- /dev/null +++ b/xsl-java/INSTALL @@ -0,0 +1,32 @@ +$Id$ + +INSTALL file for the DocBook XSL Java extensions + +---------------------------------------------------------------------- +Case #1: Installation using a package management system +---------------------------------------------------------------------- +If you've installed the DocBook XSL Java distribution using +"apt-get", "yum", "urpmi", or some similar package-management +front-end, then, as part of the package installation, the +saxon65.jar and xalan27.jar files for the extensions have most +likely already been installed in the appropriate location for your +system. To determine where the jar files are installed, consult +the specific documentation for your system (for example, the +README.Debian file). + +---------------------------------------------------------------------- +Case #2: Installing manually +---------------------------------------------------------------------- +To manually "install" the saxon65.jar and xalan27.jar files, simply +place them in a directory where you'd normally store jar files. + +---------------------------------------------------------------------- +How to use the DocBook XSL Java extensions +---------------------------------------------------------------------- +To use the DocBook XSL Java extensions, add the absolute paths to +the saxon65.jar and xalan27.jar files (wherever you may have them +installed) to your Java classpath and process your documents with +either the Saxon or Xalan-Java XSLT engines and with the value of +the DocBook XSL stylesheets "use.extensions" parameter set to 1. + +For a few more details, see the README file. diff --git a/xsl-java/Makefile b/xsl-java/Makefile new file mode 100644 index 000000000..83bd57c3f --- /dev/null +++ b/xsl-java/Makefile @@ -0,0 +1,35 @@ +include ../buildtools/Makefile.incl +include ../releasetools/Variables.mk + +DISTRO=xsl-java + +# value of DISTRIB_DEPENDS is a space-separated list of any +# targets for this distro's "distrib" target to depend on +DISTRIB_DEPENDS = + +# value of ZIP_EXCLUDES is a space-separated list of any file or +# directory names (shell wildcards OK) that should be excluded +# from the zip file and tarball for the release +DISTRIB_EXCLUDES = xsltproc saxon65 xalan27 + +ANT = ant +ANT_OPTS = + +all: jars + +jars: saxon65.jar xalan27.jar + +saxon65.jar: $(wildcard saxon65/src/com/nwalsh/saxon/*.java) + cd saxon65 && $(ANT) jar + cp saxon65/dist/saxon65.jar . + +xalan27.jar: $(wildcard xalan27/src/com/nwalsh/xalan/*.java) + cd xalan27 && $(ANT) jar + cp xalan27/dist/xalan27.jar . + +clean: + cd saxon65 && $(ANT) clean + cd xalan27 && $(ANT) clean + $(RM) *.jar + +include ../releasetools/Targets.mk diff --git a/xsl-java/README b/xsl-java/README new file mode 100644 index 000000000..06cc99556 --- /dev/null +++ b/xsl-java/README @@ -0,0 +1,52 @@ +---------------------------------------------------------------------- + README file for the DocBook XSL Java Extensions +---------------------------------------------------------------------- +$Id$ + +These are XSL Java extensions for use with the DocBook XML +stylesheets and the Saxon and Xalan-Java XSLT engines. + +This README file provides only very minimal documentation on the +DocBook Project XSL Java extensions. For more complete information +on the features the extensions provide and on how to use them +with the DocBook Project XSL stylesheets, see Bob Stayton's book +"DocBook XSL: The Complete Guide", available online at: + + http://www.sagehill.net/docbookxsl/ + +---------------------------------------------------------------------- +Installation +---------------------------------------------------------------------- +For information about installing these extensions, see the INSTALL file. + +---------------------------------------------------------------------- +How to use the DocBook XSL Java extensions +---------------------------------------------------------------------- +To use the DocBook XSL Java extensions, add the absolute paths to +the saxon65.jar and xalan27.jar files to your Java classpath and +process your documents with either the Saxon or Xalan-Java XSLT +engines and with the value of the DocBook XSL stylesheets +"use.extensions" parameter set to 1. + +---------------------------------------------------------------------- +Manifest +---------------------------------------------------------------------- +AUTHORS contact information +BUGS about known problems +COPYING copyright information +INSTALL installation instructions +README this file +TODO about planned features not yet implemented +VERSION release metadata, including the current version + number (note that the VERSION file is an XSL stylesheet) +NEWS changes since the last public release (for a cumulative + list of changes, see the ChangeHistory.xml file) + +saxon65.jar Java extensions for use with the Saxon XSLT engine +xalan27.jar Java extensions for use with the Xalan-Java XSLT engine + +---------------------------------------------------------------------- +Changes +---------------------------------------------------------------------- +See the ChangeHistory.xml.zip file for a cumulative list of all +changes. See the NEWS file for changes made since the previous release. diff --git a/xsl-java/TODO b/xsl-java/TODO new file mode 100644 index 000000000..1f421cdaf --- /dev/null +++ b/xsl-java/TODO @@ -0,0 +1,23 @@ +The "to do" list for the DocBook Project XSL stylesheets is +maintained at Sourceforge. To view a list of all open feature +requests for the stylesheets: + + http://docbook.sf.net/tracker/xsl/requests + +To submit a feature request against the stylesheets: + + http://docbook.sf.net/tracker/submit/request + +To do a full-text search of all DocBook Project issues: + + http://docbook.sf.net/tracker/search + +Discussion about the DocBook Project XSL stylesheets takes place +on the docbook-apps mailing list: + + http://wiki.docbook.org/topic/DocBookAppsMailingList + +Real-time discussion takes place on IRC: + + http://wiki.docbook.org/topic/DocBookIrcChannel + irc://irc.freenode.net/docbook diff --git a/xsl-java/VERSION b/xsl-java/VERSION new file mode 100644 index 000000000..2a6178664 --- /dev/null +++ b/xsl-java/VERSION @@ -0,0 +1,90 @@ + + + + + +docbook-xsl-java +1.72.0 +6553 +$Revision$ + + + + + DocBook + XSL Stylesheets + 1.72.1-pre + + + + + + +Major feature enhancements + + + + + + http://sourceforge.net/projects/docbook/ + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.tar.gz?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.zip?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.bz2?download + http://sourceforge.net/project/shownotes.php?release_id={SFRELID} + http://docbook.cvs.sourceforge.net/docbook/ + http://lists.oasis-open.org/archives/docbook-apps/ + This is a bug-fix release with a few feature enhancements. + + + + + + + You must specify the sf-relid as a parameter. + + + + + + + + + + + + + + + + + + : + + + + + + + + + : + + + + + + + + + : + + + + + diff --git a/xsl-java/saxon65/build.xml b/xsl-java/saxon65/build.xml new file mode 100644 index 000000000..8c065fa15 --- /dev/null +++ b/xsl-java/saxon65/build.xml @@ -0,0 +1,69 @@ + + + + + + Builds, tests, and runs the project saxon65. + + + diff --git a/xsl-java/saxon65/nbproject/build-impl.xml b/xsl-java/saxon65/nbproject/build-impl.xml new file mode 100644 index 000000000..9624598ec --- /dev/null +++ b/xsl-java/saxon65/nbproject/build-impl.xml @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set platform.home + Must set platform.bootcp + Must set platform.java + Must set platform.javac + Platform is not correctly set up + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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: + + + + + + + ${platform.java} -cp "${run.classpath.with.dist.jar}" ${main.class} + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + ${platform.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-java/saxon65/nbproject/genfiles.properties b/xsl-java/saxon65/nbproject/genfiles.properties new file mode 100644 index 000000000..f20b70278 --- /dev/null +++ b/xsl-java/saxon65/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=8e458dcb +build.xml.script.CRC32=c0233ac0 +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=8e458dcb +nbproject/build-impl.xml.script.CRC32=39e8d752 +nbproject/build-impl.xml.stylesheet.CRC32=99b91518 diff --git a/xsl-java/saxon65/nbproject/project.properties b/xsl-java/saxon65/nbproject/project.properties new file mode 100644 index 000000000..a0924a400 --- /dev/null +++ b/xsl-java/saxon65/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}/saxon65.jar +dist.javadoc.dir=${dist.dir}/javadoc +file.reference.saxon.jar=../../../../../usr/local/java/saxon6/saxon.jar +jar.compress=false +javac.classpath=\ + ${file.reference.saxon.jar} +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.source=1.4 +javac.target=1.4 +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=Java_HotSpot_TM__Client_VM_1.4.2_11-b06 +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-java/saxon65/nbproject/project.xml b/xsl-java/saxon65/nbproject/project.xml new file mode 100644 index 000000000..3f4c7e7f4 --- /dev/null +++ b/xsl-java/saxon65/nbproject/project.xml @@ -0,0 +1,17 @@ + + + org.netbeans.modules.java.j2seproject + + + saxon65 + 1.6.5 + + + + + + + + + + diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/CVS.java b/xsl-java/saxon65/src/com/nwalsh/saxon/CVS.java new file mode 100644 index 000000000..ca89f39b6 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/CVS.java @@ -0,0 +1,90 @@ +package com.nwalsh.saxon; + +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; + +/** + *

Saxon extension to convert CVS date strings into local time

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon + * 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 static 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-java/saxon65/src/com/nwalsh/saxon/Callout.java b/xsl-java/saxon65/src/com/nwalsh/saxon/Callout.java new file mode 100644 index 000000000..cffa054fe --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/Callout.java @@ -0,0 +1,90 @@ +package com.nwalsh.saxon; + +import org.w3c.dom.*; + +/** + *

A class for maintaining information about callouts.

+ * + *

To make processing callouts easier, they are parsed out of the + * input structure and stored in a sorted array. (The array is sorted + * according to the order in which the callouts occur.)

+ * + *

This class is just the little record + * that we store in the array for each callout.

+ */ +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 constructor; initialize the private data structures. */ + public Callout(int callout, Element area, int line, int col) { + this.callout = callout; + this.area = area; + this.line = line; + this.col = col; + } + + /** + *

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; + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/CalloutEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/CalloutEmitter.java new file mode 100644 index 000000000..a61b38c3b --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/CalloutEmitter.java @@ -0,0 +1,532 @@ +package com.nwalsh.saxon; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.Controller; +import com.icl.saxon.om.NamePool; +import com.icl.saxon.output.Emitter; +import com.icl.saxon.tree.AttributeCollection; + +/** + *

Saxon extension to decorate a result tree fragment with callouts.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides the guts of a + * Saxon 6.* + * implementation of callouts for verbatim environments. (It is used + * by the Verbatim class.)

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some verbatim environment. The Verbatim class initializes + * a CalloutEmitter with information about the callouts that should be applied + * to the verbatim environment in question. Then the result tree fragment + * is "replayed" through the CalloutEmitter; the CalloutEmitter builds a + * new result tree fragment from this event stream, decorated with callouts, + * and that is returned.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @see Verbatim + * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class CalloutEmitter extends CopyEmitter { + /** A stack for the preserving information about open elements. */ + protected Stack elementStack = null; + + /** A stack for holding information about temporarily closed elements. */ + protected Stack tempStack = null; + + /** Is the next element absolutely the first element in the fragment? */ + protected boolean firstElement = false; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** The XHTML namespace name. */ + protected static String xhURI = "http://www.w3.org/1999/xhtml"; + + /** The default column for callouts that specify only a line. */ + protected int defaultColumn = 60; + + /** Is the stylesheet currently running an FO stylesheet? */ + protected boolean foStylesheet = false; + + /** The current line number. */ + private static int lineNumber = 0; + + /** The current column number. */ + private static int colNumber = 0; + + /** The (sorted) array of callouts obtained from the areaspec. */ + private static Callout callout[] = null; + + /** The number of callouts in the callout array. */ + private static int calloutCount = 0; + + /** A pointer used to keep track of our position in the callout array. */ + private static int calloutPos = 0; + + /** The FormatCallout object to use for formatting callouts. */ + private static FormatCallout fCallout = null; + + /**

Constructor for the CalloutEmitter.

+ * + * @param controller + * @param namePool The name pool to use for constructing elements and attributes. + * @param defaultColumn The default column for callouts. + * @param foStylesheet Is this an FO stylesheet? + * @param fCallout + */ + public CalloutEmitter(Controller controller, + NamePool namePool, + int defaultColumn, + boolean foStylesheet, + FormatCallout fCallout) { + super(controller, namePool); + elementStack = new Stack(); + firstElement = true; + + this.defaultColumn = defaultColumn; + this.foStylesheet = foStylesheet; + this.fCallout = fCallout; + } + + /** + *

Examine the areaspec and determine the number and position of + * callouts.

+ * + *

The areaspecNodeSet + * is examined and a sorted list of the callouts is constructed.

+ * + *

This data structure is used to augment the result tree fragment + * with callout bullets.

+ * + * @param areaspecNodeList The source document <areaspec> element. + */ + public void setupCallouts (NodeList areaspecNodeList) { + 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 areaspec = areaspecNodeList.item(0); + NodeList children = areaspec.getChildNodes(); + + for (int count = 0; count < children.getLength(); count++) { + Node node = children.item(count); + if (node.getNodeType() == Node.ELEMENT_NODE) { + if (node.getNodeName().equalsIgnoreCase("areaset")) { + coNum++; + NodeList areas = node.getChildNodes(); + for (int acount = 0; acount < areas.getLength(); acount++) { + Node area = areas.item(acount); + if (area.getNodeType() == Node.ELEMENT_NODE) { + if (area.getNodeName().equalsIgnoreCase("area")) { + addCallout(coNum, area, defaultColumn); + } else { + System.out.println("Unexpected element in areaset: " + + area.getNodeName()); + } + } + } + } else if (node.getNodeName().equalsIgnoreCase("area")) { + coNum++; + addCallout(coNum, node, defaultColumn); + } else { + System.out.println("Unexpected element in areaspec: " + + node.getNodeName()); + } + } + } + + // Now sort them + java.util.Arrays.sort(callout, 0, calloutCount); + } + + /** Process characters. */ + public void characters(char[] chars, int start, int len) + throws TransformerException { + + // If we hit characters, then there's no first element... + firstElement = false; + + if (lineNumber == 0) { + // if there are any text nodes, there's at least one line + lineNumber++; + colNumber = 1; + } + + // Walk through the text node looking for callout positions + char[] newChars = new char[len]; + int pos = 0; + for (int count = start; count < start+len; count++) { + if (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + if (pos > 0) { + rtfEmitter.characters(newChars, 0, pos); + pos = 0; + } + + closeOpenElements(rtfEmitter); + + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + fCallout.formatCallout(rtfEmitter, callout[calloutPos]); + calloutPos++; + } + + openClosedElements(rtfEmitter); + } + + if (chars[count] == '\n') { + // What if we need to pad this line? + if (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() > colNumber) { + + if (pos > 0) { + rtfEmitter.characters(newChars, 0, pos); + pos = 0; + } + + closeOpenElements(rtfEmitter); + + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() > colNumber) { + formatPad(callout[calloutPos].getColumn() - colNumber); + colNumber = callout[calloutPos].getColumn(); + while (calloutPos < calloutCount + && callout[calloutPos].getLine() == lineNumber + && callout[calloutPos].getColumn() == colNumber) { + fCallout.formatCallout(rtfEmitter, callout[calloutPos]); + calloutPos++; + } + } + + openClosedElements(rtfEmitter); + } + + lineNumber++; + colNumber = 1; + } else { + colNumber++; + } + newChars[pos++] = chars[count]; + } + + if (pos > 0) { + rtfEmitter.characters(newChars, 0, pos); + } + } + + /** + *

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 numBlanks The number of blanks to add. + */ + protected void formatPad(int numBlanks) { + char chars[] = new char[numBlanks]; + for (int count = 0; count < numBlanks; count++) { + chars[count] = ' '; + } + + try { + rtfEmitter.characters(chars, 0, numBlanks); + } catch (TransformerException e) { + System.out.println("Transformer Exception in formatPad"); + } + } + + /** + *

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. + */ + protected void addCallout (int coNum, + Node node, + int defaultColumn) { + + Element area = (Element) node; + String units = null; + String coords = null; + + if (area.hasAttribute("units")) { + units = area.getAttribute("units"); + } + + if (area.hasAttribute("coords")) { + coords = area.getAttribute("coords"); + } + + if (units != null + && !units.equalsIgnoreCase("linecolumn") + && !units.equalsIgnoreCase("linerange")) { + 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 (units != null && units.equalsIgnoreCase("linerange")) { + for (int count = c1; count <= c2; count++) { + callout[calloutCount++] = new Callout(coNum, area, + count, defaultColumn); + } + } else { + // assume linecolumn + callout[calloutCount++] = new Callout(coNum, area, c1, c2); + } + } else { + // if there's only one number, assume it's the line + callout[calloutCount++] = new Callout(coNum, area, c1, defaultColumn); + } + } + + /** Process end element events. */ + public void endElement(int nameCode) + throws TransformerException { + + if (!elementStack.empty()) { + // if we didn't push the very first element (an fo:block or + // pre or div surrounding the whole block), then the stack will + // be empty when we get to the end of the first element... + elementStack.pop(); + } + rtfEmitter.endElement(nameCode); + } + + /** Process start element events. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) + throws TransformerException { + + if (!skipThisElement(nameCode)) { + StartElementInfo sei = new StartElementInfo(nameCode, attributes, + namespaces, nscount); + elementStack.push(sei); + } + + firstElement = false; + + rtfEmitter.startElement(nameCode, attributes, namespaces, nscount); + } + + /** + *

Protect the outer-most block wrapper.

+ * + *

Open elements in the result tree fragment are closed and reopened + * around callouts (so that callouts don't appear inside links or other + * environments). But if the result tree fragment is a single block + * (a div or pre in HTML, an fo:block in FO), that outer-most block is + * treated specially.

+ * + *

This method returns true if the element in question is that + * outermost block.

+ * + * @param nameCode The name code for the element + * + * @return True if the element is the outer-most block, false otherwise. + */ + protected boolean skipThisElement(int nameCode) { + // FIXME: This is such a gross hack... + if (firstElement) { + int thisFingerprint = namePool.getFingerprint(nameCode); + int foBlockFingerprint = namePool.getFingerprint(foURI, "block"); + int htmlPreFingerprint = namePool.getFingerprint("", "pre"); + int htmlDivFingerprint = namePool.getFingerprint("", "div"); + int xhtmlPreFingerprint = namePool.getFingerprint(xhURI, "pre"); + int xhtmlDivFingerprint = namePool.getFingerprint(xhURI, "div"); + + if ((foStylesheet && thisFingerprint == foBlockFingerprint) + || (!foStylesheet && (thisFingerprint == htmlPreFingerprint + || thisFingerprint == htmlDivFingerprint + || thisFingerprint == xhtmlPreFingerprint + || thisFingerprint == xhtmlDivFingerprint))) { + // Don't push the outer-most wrapping div, pre, or fo:block + return true; + } + } + + return false; + } + + private void closeOpenElements(Emitter rtfEmitter) + throws TransformerException { + // Close all the open elements... + tempStack = new Stack(); + while (!elementStack.empty()) { + StartElementInfo elem = (StartElementInfo) elementStack.pop(); + rtfEmitter.endElement(elem.getNameCode()); + tempStack.push(elem); + } + } + + private void openClosedElements(Emitter rtfEmitter) + throws TransformerException { + // Now "reopen" the elements that we closed... + while (!tempStack.empty()) { + StartElementInfo elem = (StartElementInfo) tempStack.pop(); + AttributeCollection attr = (AttributeCollection) elem.getAttributes(); + AttributeCollection newAttr = new AttributeCollection(namePool); + + for (int acount = 0; acount < attr.getLength(); acount++) { + String localName = attr.getLocalName(acount); + int nameCode = attr.getNameCode(acount); + String type = attr.getType(acount); + String value = attr.getValue(acount); + String uri = attr.getURI(acount); + String prefix = ""; + + if (localName.indexOf(':') > 0) { + prefix = localName.substring(0, localName.indexOf(':')); + localName = localName.substring(localName.indexOf(':')+1); + } + + if (uri.equals("") + && ((foStylesheet + && localName.equals("id")) + || (!foStylesheet + && (localName.equals("id") + || localName.equals("name"))))) { + // skip this attribute + } else { + newAttr.addAttribute(prefix, uri, localName, type, value); + } + } + + rtfEmitter.startElement(elem.getNameCode(), + newAttr, + elem.getNamespaces(), + elem.getNSCount()); + + elementStack.push(elem); + } + } + + /** + *

A private class for maintaining the information required to call + * the startElement method.

+ * + *

In order to close and reopen elements, information about those + * elements has to be maintained. This class is just the little record + * that we push on the stack to keep track of that info.

+ */ + private class StartElementInfo { + private int _nameCode; + org.xml.sax.Attributes _attributes; + int[] _namespaces; + int _nscount; + + public StartElementInfo(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) { + _nameCode = nameCode; + _attributes = attributes; + _namespaces = namespaces; + _nscount = nscount; + } + + public int getNameCode() { + return _nameCode; + } + + public org.xml.sax.Attributes getAttributes() { + return _attributes; + } + + public int[] getNamespaces() { + return _namespaces; + } + + public int getNSCount() { + return _nscount; + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnScanEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnScanEmitter.java new file mode 100644 index 000000000..6efe35aab --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnScanEmitter.java @@ -0,0 +1,170 @@ +package com.nwalsh.saxon; + +import org.xml.sax.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.output.*; +import com.icl.saxon.om.*; +import com.icl.saxon.expr.FragmentValue; + +/** + *

Saxon extension to scan the column widths in a result tree fragment.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon 6.* + * implementation to scan the column widths in a result tree + * fragment.

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some colgroup environment. That result tree fragment + * is "replayed" through the ColumnScanEmitter; the ColumnScanEmitter watches + * the cols go by and extracts the column widths that it sees. These + * widths are then made available.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class ColumnScanEmitter extends com.icl.saxon.output.Emitter { + /** The number of columns seen. */ + protected int numColumns = 0; + protected String width[] = new String[5]; + protected NamePool namePool = null; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** Construct a new ColumnScanEmitter. */ + public ColumnScanEmitter(NamePool namePool) { + numColumns = 0; + this.namePool = namePool; + } + + /** Return the number of columns. */ + public int columnCount() { + return numColumns; + } + + /** Return the number of columns. */ + public String[] columnWidths() { + // Return a width array with exactly the right number of columns + String rWidth[] = new String[numColumns]; + for (int count = 0; count < numColumns; count++) { + rWidth[count] = width[count]; + } + return rWidth; + } + + /** Discarded. */ + public void characters(char[] chars, int start, int len) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void comment(char[] chars, int start, int length) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void endDocument() + throws TransformerException { + // nop + } + + /** Discarded. */ + public void endElement(int nameCode) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void processingInstruction(java.lang.String name, + java.lang.String data) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void setDocumentLocator(org.xml.sax.Locator locator) { + // nop + } + + /** Discarded. */ + public void setEscaping(boolean escaping) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void setNamePool(NamePool namePool) { + // nop + } + + /** Discarded. */ + public void setUnparsedEntity(java.lang.String name, java.lang.String uri) + throws TransformerException { + // nop + } + + /** Discarded. */ + public void setWriter(java.io.Writer writer) { + // nop + } + + /** Discarded. */ + public void startDocument() + throws TransformerException { + // nop + } + + /** Examine for column info. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, int nscount) + throws TransformerException { + + int thisFingerprint = namePool.getFingerprint(nameCode); + int colFingerprint = namePool.getFingerprint("", "col"); + int foColFingerprint = namePool.getFingerprint(foURI, "table-column"); + + if (thisFingerprint == colFingerprint + || thisFingerprint == foColFingerprint) { + if (numColumns >= width.length) { + String newWidth[] = new String[width.length+10]; + for (int count = 0; count < width.length; count++) { + newWidth[count] = width[count]; + } + width = newWidth; + } + + if (thisFingerprint == colFingerprint) { + if (attributes.getValue("width") == null) { + width[numColumns++] = "1*"; + } else { + width[numColumns++] = attributes.getValue("width"); + } + } else { + if (attributes.getValue("column-width") == null) { + width[numColumns++] = "1*"; + } else { + width[numColumns++] = attributes.getValue("column-width"); + } + } + } + } +} + + diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnUpdateEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnUpdateEmitter.java new file mode 100644 index 000000000..36434caba --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/ColumnUpdateEmitter.java @@ -0,0 +1,96 @@ +package com.nwalsh.saxon; + +import org.xml.sax.*; +import com.icl.saxon.output.*; +import com.icl.saxon.Controller; +import com.icl.saxon.om.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.expr.FragmentValue; +import com.icl.saxon.tree.AttributeCollection; + +/** + *

Saxon extension to scan the column widths in a result tree fragment.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon 6.* + * implementation to scan the column widths in a result tree + * fragment.

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some colgroup environment. That result tree fragment + * is "replayed" through the ColumnUpdateEmitter; the ColumnUpdateEmitter watches + * the cols go by and extracts the column widths that it sees. These + * widths are then made available.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class ColumnUpdateEmitter extends CopyEmitter { + /** The number of columns seen. */ + protected int numColumns = 0; + protected String width[] = null; + protected NamePool namePool = null; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** Construct a new ColumnUpdateEmitter. */ + public ColumnUpdateEmitter(Controller controller, + NamePool namePool, + String width[]) { + super(controller, namePool); + numColumns = 0; + this.width = width; + this.namePool = namePool; + } + + /** Examine for column info. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, int nscount) + throws TransformerException { + + int thisFingerprint = namePool.getFingerprint(nameCode); + int colFingerprint = namePool.getFingerprint("", "col"); + int foColFingerprint = namePool.getFingerprint(foURI, "table-column"); + + if (thisFingerprint == colFingerprint) { + AttributeCollection attr = new AttributeCollection(namePool, attributes); + int widthFingerprint = namePool.getFingerprint("", "width"); + + if (attr.getValueByFingerprint(widthFingerprint) == null) { + attr.addAttribute(widthFingerprint, "CDATA", width[numColumns++]); + } else { + attr.setAttribute(widthFingerprint, "CDATA", width[numColumns++]); + } + attributes = attr; + } else if (thisFingerprint == foColFingerprint) { + AttributeCollection attr = new AttributeCollection(namePool, attributes); + int widthFingerprint = namePool.getFingerprint("", "column-width"); + + if (attr.getValueByFingerprint(widthFingerprint) == null) { + attr.addAttribute(widthFingerprint, "CDATA", width[numColumns++]); + } else { + attr.setAttribute(widthFingerprint, "CDATA", width[numColumns++]); + } + attributes = attr; + } + + rtfEmitter.startElement(nameCode, attributes, namespaces, nscount); + } +} + + diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/CopyEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/CopyEmitter.java new file mode 100644 index 000000000..3718c50d1 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/CopyEmitter.java @@ -0,0 +1,150 @@ +package com.nwalsh.saxon; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.Context; +import com.icl.saxon.expr.*; +import com.icl.saxon.expr.FragmentValue; +import com.icl.saxon.Controller; +import com.icl.saxon.functions.Extensions; +import com.icl.saxon.om.*; +import com.icl.saxon.output.*; +import com.icl.saxon.pattern.*; +import com.icl.saxon.tree.*; + +/** + *

A Saxon 6.0 Emitter that clones its input.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon 6.* + * implementation of an emitter that manufactures a cloned result + * tree fragment.

+ * + *

The purpose of this emitter is to provide something for + * CalloutEmitter and NumberLinesEmitter to extend. + * This emitter simply copies all input to a new result tree fragment.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @see CalloutEmitter + * @see NumberLinesEmitter + * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class CopyEmitter extends com.icl.saxon.output.Emitter { + /** The result tree fragment containing the copied fragment. */ + protected FragmentValue rtf = null; + protected Emitter rtfEmitter = null; + + /**

The namePool.

+ * + *

Copied from the caller, it should be the runtime name pool.

+ */ + protected NamePool namePool = null; + + /**

Constructor for the CopyEmitter.

+ * + * @param namePool The name pool to use for constructing elements and attributes. + */ + public CopyEmitter(Controller controller, NamePool namePool) { + rtf = new FragmentValue(controller); + rtfEmitter = rtf.getEmitter(); + this.namePool = namePool; + } + + /** + *

Return the result tree fragment constructed by replaying events + * through this emitter.

+ */ + public FragmentValue getResultTreeFragment() { + return rtf; + } + + /** Copy characters. */ + public void characters(char[] chars, int start, int len) + throws TransformerException { + rtfEmitter.characters(chars, start, len); + } + + /** Copy comments. */ + public void comment(char[] chars, int start, int length) + throws TransformerException { + rtfEmitter.comment(chars, start, length); + } + + /** Copy end document events. */ + public void endDocument() + throws TransformerException { + rtfEmitter.endDocument(); + } + + /** Copy end element events. */ + public void endElement(int nameCode) + throws TransformerException { + rtfEmitter.endElement(nameCode); + } + + /** Copy processing instructions. */ + public void processingInstruction(java.lang.String name, + java.lang.String data) + throws TransformerException { + rtfEmitter.processingInstruction(name, data); + } + + /** Copy set document locator events. */ + public void setDocumentLocator(org.xml.sax.Locator locator) { + rtfEmitter.setDocumentLocator(locator); + } + + /** Copy set escaping events. */ + public void setEscaping(boolean escaping) + throws TransformerException { + rtfEmitter.setEscaping(escaping); + } + + /** Copy set name pool events. */ + public void setNamePool(NamePool namePool) { + rtfEmitter.setNamePool(namePool); + } + + /** Copy set unparsed entity events. */ + public void setUnparsedEntity(java.lang.String name, java.lang.String uri) + throws TransformerException { + rtfEmitter.setUnparsedEntity(name, uri); + } + + /** Copy set writer events. */ + public void setWriter(java.io.Writer writer) { + rtfEmitter.setWriter(writer); + } + + /** Copy start document events. */ + public void startDocument() + throws TransformerException { + rtfEmitter.startDocument(); + } + + /** Copy start element events. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) + throws TransformerException { + rtfEmitter.startElement(nameCode, attributes, namespaces, nscount); + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/FormatCallout.java b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatCallout.java new file mode 100644 index 000000000..256a3b513 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatCallout.java @@ -0,0 +1,111 @@ +package com.nwalsh.saxon; + +import org.xml.sax.SAXException; +import org.w3c.dom.*; + +import javax.xml.transform.TransformerException; + +import com.icl.saxon.om.NamePool; +import com.icl.saxon.output.Emitter; +import com.icl.saxon.tree.AttributeCollection; + +import com.nwalsh.saxon.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 foStylesheet = false; + protected NamePool namePool = null; + + public FormatCallout(NamePool nPool, boolean fo) { + namePool = nPool; + foStylesheet = fo; + } + + public String areaLabel(Element area) { + String label = null; + + if (area.hasAttribute("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.getLocalName().equalsIgnoreCase("areaset") + && parent.hasAttribute("label")) { + label = parent.getAttribute("label"); + } + } + + return label; + } + + public void startSpan(Emitter rtf) + throws TransformerException { + // no point in doing this for FO, right? + if (!foStylesheet && namePool != null) { + int spanName = namePool.allocate("", "", "span"); + AttributeCollection spanAttr = new AttributeCollection(namePool); + int namespaces[] = new int[1]; + spanAttr.addAttribute("", "", "class", "CDATA", "co"); + rtf.startElement(spanName, spanAttr, namespaces, 0); + } + } + + public void endSpan(Emitter rtf) + throws TransformerException { + // no point in doing this for FO, right? + if (!foStylesheet && namePool != null) { + int spanName = namePool.allocate("", "", "span"); + rtf.endElement(spanName); + } + } + + public void formatTextCallout(Emitter rtfEmitter, + 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(rtfEmitter); + rtfEmitter.characters(chars, 0, label.length()); + endSpan(rtfEmitter); + } catch (TransformerException e) { + System.out.println("Transformer Exception in formatTextCallout"); + } + } + + public abstract void formatCallout(Emitter rtfEmitter, + Callout callout); +} + diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/FormatGraphicCallout.java b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatGraphicCallout.java new file mode 100644 index 000000000..c2f16d95a --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatGraphicCallout.java @@ -0,0 +1,89 @@ +package com.nwalsh.saxon; + +import org.xml.sax.SAXException; +import org.w3c.dom.*; + +import javax.xml.transform.TransformerException; + +import com.icl.saxon.om.NamePool; +import com.icl.saxon.output.Emitter; +import com.icl.saxon.tree.AttributeCollection; + +import com.nwalsh.saxon.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 class FormatGraphicCallout extends FormatCallout { + String graphicsPath = ""; + String graphicsExt = ""; + int graphicsMax = 0; + + public FormatGraphicCallout(NamePool nPool, String path, String ext, int max, boolean fo) { + super(nPool, fo); + graphicsPath = path; + graphicsExt = ext; + graphicsMax = max; + } + + public void formatCallout(Emitter rtfEmitter, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String userLabel = areaLabel(area); + String label = "(" + num + ")"; + + if (userLabel != null) { + label = userLabel; + } + + try { + if (userLabel == null && num <= graphicsMax) { + int imgName = 0; + AttributeCollection imgAttr = null; + int namespaces[] = new int[1]; + + if (foStylesheet) { + imgName = namePool.allocate("fo", foURI, "external-graphic"); + imgAttr = new AttributeCollection(namePool); + imgAttr.addAttribute("", "", "src", "CDATA", "url(" + + graphicsPath + num + graphicsExt + ")"); + + } else { + imgName = namePool.allocate("", "", "img"); + imgAttr = new AttributeCollection(namePool); + imgAttr.addAttribute("", "", "src", "CDATA", + graphicsPath + num + graphicsExt); + imgAttr.addAttribute("", "", "alt", "CDATA", label); + } + + startSpan(rtfEmitter); + rtfEmitter.startElement(imgName, imgAttr, namespaces, 0); + rtfEmitter.endElement(imgName); + endSpan(rtfEmitter); + } else { + formatTextCallout(rtfEmitter, callout); + } + } catch (TransformerException e) { + System.out.println("Transformer Exception in graphic formatCallout"); + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/FormatTextCallout.java b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatTextCallout.java new file mode 100644 index 000000000..6e2d75c1b --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatTextCallout.java @@ -0,0 +1,43 @@ +package com.nwalsh.saxon; + +import org.xml.sax.SAXException; +import org.w3c.dom.*; + +import javax.xml.transform.TransformerException; + +import com.icl.saxon.om.NamePool; +import com.icl.saxon.output.Emitter; + +import com.nwalsh.saxon.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 class FormatTextCallout extends FormatCallout { + public FormatTextCallout(NamePool nPool, boolean fo) { + super(nPool, fo); + } + + public void formatCallout(Emitter rtfEmitter, + Callout callout) { + formatTextCallout(rtfEmitter, callout); + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/FormatUnicodeCallout.java b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatUnicodeCallout.java new file mode 100644 index 000000000..e51e1c074 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/FormatUnicodeCallout.java @@ -0,0 +1,98 @@ +package com.nwalsh.saxon; + +import org.xml.sax.SAXException; +import org.w3c.dom.*; + +import javax.xml.transform.TransformerException; + +import com.icl.saxon.om.NamePool; +import com.icl.saxon.output.Emitter; +import com.icl.saxon.tree.AttributeCollection; + +import com.nwalsh.saxon.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 class FormatUnicodeCallout extends FormatCallout { + int unicodeMax = 0; + int unicodeStart = 0; + String unicodeFont = ""; + + public FormatUnicodeCallout(NamePool nPool, + String font, + int start, + int max, + boolean fo) { + super(nPool, fo); + unicodeFont = font; + unicodeMax = max; + unicodeStart = start; + } + + public void formatCallout(Emitter rtfEmitter, + Callout callout) { + Element area = callout.getArea(); + int num = callout.getCallout(); + String userLabel = areaLabel(area); + String label = ""; + + if (userLabel != null) { + label = userLabel; + } + + try { + if (userLabel == null && num <= unicodeMax) { + int inName = 0; + AttributeCollection inAttr = null; + int namespaces[] = new int[1]; + + if (!unicodeFont.equals("")) { + if (foStylesheet) { + inName = namePool.allocate("fo", foURI, "inline"); + inAttr = new AttributeCollection(namePool); + inAttr.addAttribute("", "", "font-family", "CDATA", unicodeFont); + } else { + inName = namePool.allocate("", "", "font"); + inAttr = new AttributeCollection(namePool); + inAttr.addAttribute("", "", "face", "CDATA", unicodeFont); + } + + startSpan(rtfEmitter); + rtfEmitter.startElement(inName, inAttr, namespaces, 0); + } + + char chars[] = new char[1]; + chars[0] = (char) (unicodeStart + num - 1); + rtfEmitter.characters(chars, 0, 1); + + if (!unicodeFont.equals("")) { + rtfEmitter.endElement(inName); + endSpan(rtfEmitter); + } + } else { + formatTextCallout(rtfEmitter, callout); + } + } catch (TransformerException e) { + System.out.println("Transformer Exception in graphic formatCallout"); + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/ImageIntrinsics.java b/xsl-java/saxon65/src/com/nwalsh/saxon/ImageIntrinsics.java new file mode 100644 index 000000000..9f6b9e419 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/ImageIntrinsics.java @@ -0,0 +1,189 @@ +package com.nwalsh.saxon; + +import java.io.File; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.net.MalformedURLException; +import java.awt.Toolkit; +import java.awt.Image; +import java.awt.image.ImageObserver; +import java.lang.Thread; +import java.util.StringTokenizer; + +/** + *

Saxon extension to examine intrinsic size of images

+ * + *

$Id$

+ * + *

Copyright (C) 2002 Norman Walsh.

+ * + *

This class provides a + * Saxon + * 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(String imageFn) { + System.setProperty("java.awt.headless","true"); + + // Hack. I expect the right way to do this is to always use a URL. + // However, that means getting the base URI correct and dealing + // with the various permutations of the file: URI scheme on different + // platforms. So instead, what we're going to do is cheat. If it + // starts with http: or ftp:, call it a URI. Otherwise, call it + // a file. Also call it a file if the URI is malformed. + + URL imageUrl = null; + + if (imageFn.startsWith("http:") || imageFn.startsWith("ftp:") || imageFn.startsWith("file:")) { + try { + imageUrl = new URL(imageFn); + } catch (MalformedURLException mue) { + imageUrl = null; + } + } + + if (imageUrl != null) { + image = Toolkit.getDefaultToolkit().getImage (imageUrl); + } else { + 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 (and doesn't handle the URL case!!!) + 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(); + } + } else { + System.err.println("Failed to interpret image: " + imageFn); + } + } catch (Exception e) { + System.err.println("Failed to load image: " + imageFn); + } + + if (ir != null) { + try { + ir.close(); + } catch (Exception e) { + // nop; + } + } + } + } + + public int getWidth(int defaultWidth) { + if (width >= 0) { + return width; + } else { + return defaultWidth; + } + } + + public int getDepth(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-java/saxon65/src/com/nwalsh/saxon/LineCountEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/LineCountEmitter.java new file mode 100644 index 000000000..d8110dedb --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/LineCountEmitter.java @@ -0,0 +1,141 @@ +package com.nwalsh.saxon; + +import org.xml.sax.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.output.*; +import com.icl.saxon.om.*; +import com.icl.saxon.expr.FragmentValue; + +/** + *

Saxon extension to count the lines in a result tree fragment.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon 6.* + * implementation to count the number of lines in a result tree + * fragment.

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some verbatim environment. That result tree fragment + * is "replayed" through the LineCountEmitter; the LineCountEmitter watches + * characters go by and counts the number of line feeds that it sees. + * That number is then returned.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @see Verbatim + * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class LineCountEmitter extends com.icl.saxon.output.Emitter { + /** The number of lines seen. */ + protected int numLines = 0; + + /** Construct a new LineCountEmitter. */ + public LineCountEmitter() { + numLines = 0; + } + + /** Reset the number of lines. */ + public void reset() { + numLines = 0; + } + + /** Return the number of lines. */ + public int lineCount() { + return numLines; + } + + /** Process characters. */ + public void characters(char[] chars, int start, int len) + throws javax.xml.transform.TransformerException { + + if (numLines == 0) { + // If there are any characters at all, there's at least one line + numLines++; + } + + for (int count = start; count < start+len; count++) { + if (chars[count] == '\n') { + numLines++; + } + } + } + + /** Discarded. */ + public void comment(char[] chars, int start, int length) + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void endDocument() + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void endElement(int nameCode) + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void processingInstruction(java.lang.String name, + java.lang.String data) + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void setDocumentLocator(org.xml.sax.Locator locator) { + // nop + } + + /** Discarded. */ + public void setEscaping(boolean escaping) + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void setNamePool(NamePool namePool) { + // nop + } + + /** Discarded. */ + public void setUnparsedEntity(java.lang.String name, java.lang.String uri) + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void setWriter(java.io.Writer writer) { + // nop + } + + /** Discarded. */ + public void startDocument() + throws javax.xml.transform.TransformerException { + // nop + } + + /** Discarded. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, int nscount) + throws javax.xml.transform.TransformerException { + // nop + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/NumberLinesEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/NumberLinesEmitter.java new file mode 100644 index 000000000..c32efa9e3 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/NumberLinesEmitter.java @@ -0,0 +1,338 @@ +package com.nwalsh.saxon; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.output.*; +import com.icl.saxon.om.*; +import com.icl.saxon.Controller; +import com.icl.saxon.tree.AttributeCollection; +import com.icl.saxon.expr.FragmentValue; + +/** + *

Saxon extension to decorate a result tree fragment with line numbers.

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides the guts of a + * Saxon 6.* + * implementation of line numbering for verbatim environments. (It is used + * by the Verbatim class.)

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some verbatim environment. The Verbatim class initializes + * a NumberLinesEmitter with information about what lines should be + * numbered and how. Then the result tree fragment + * is "replayed" through the NumberLinesEmitter; the NumberLinesEmitter + * builds a + * new result tree fragment from this event stream, decorated with line + * numbers, + * and that is returned.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @see Verbatim + * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class NumberLinesEmitter extends CopyEmitter { + /** A stack for the preserving information about open elements. */ + protected Stack elementStack = null; + + /** The current line number. */ + protected int lineNumber = 0; + + /** Is the next element absolutely the first element in the fragment? */ + protected boolean firstElement = false; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** The XHTML namespace name. */ + protected static String xhURI = "http://www.w3.org/1999/xhtml"; + + /** The first line number will be startinglinenumber. */ + protected int startinglinenumber = 1; + + /** Every modulus line will be numbered. */ + protected int modulus = 5; + + /** Line numbers are width characters wide. */ + protected int width = 3; + + /** Line numbers are separated from the listing by separator. */ + protected String separator = " "; + + /** Is the stylesheet currently running an FO stylesheet? */ + protected boolean foStylesheet = false; + + /**

Constructor for the NumberLinesEmitter.

+ * + * @param namePool The name pool to use for constructing elements and attributes. + * @param modulus The modulus to use for this listing. + * @param width The width to use for line numbers in this listing. + * @param separator The separator to use for this listing. + * @param foStylesheet Is this an FO stylesheet? + */ + public NumberLinesEmitter(Controller controller, + NamePool namePool, + int startingLineNumber, + int modulus, + int width, + String separator, + boolean foStylesheet) { + super(controller,namePool); + elementStack = new Stack(); + firstElement = true; + + this.modulus = modulus; + this.startinglinenumber = startingLineNumber; + this.width = width; + this.separator = separator; + this.foStylesheet = foStylesheet; + } + + /** Process characters. */ + public void characters(char[] chars, int start, int len) + throws TransformerException { + + // If we hit characters, then there's no first element... + firstElement = false; + + if (lineNumber == 0) { + // The first line is always numbered + lineNumber = startinglinenumber; + formatLineNumber(lineNumber); + } + + // Walk through the text node looking for newlines + char[] newChars = new char[len]; + int pos = 0; + for (int count = start; count < start+len; count++) { + if (chars[count] == '\n') { + // This is the tricky bit; if we find a newline, make sure + // it doesn't occur inside any markup. + + if (pos > 0) { + // Output any characters that preceded this newline + rtfEmitter.characters(newChars, 0, pos); + pos = 0; + } + + // Close all the open elements... + Stack tempStack = new Stack(); + while (!elementStack.empty()) { + StartElementInfo elem = (StartElementInfo) elementStack.pop(); + rtfEmitter.endElement(elem.getNameCode()); + tempStack.push(elem); + } + + // Copy the newline to the output + newChars[pos++] = chars[count]; + rtfEmitter.characters(newChars, 0, pos); + pos = 0; + + // Add the line number + formatLineNumber(++lineNumber); + + // Now "reopen" the elements that we closed... + while (!tempStack.empty()) { + StartElementInfo elem = (StartElementInfo) tempStack.pop(); + AttributeCollection attr = (AttributeCollection)elem.getAttributes(); + AttributeCollection newAttr = new AttributeCollection(namePool); + + for (int acount = 0; acount < attr.getLength(); acount++) { + String localName = attr.getLocalName(acount); + int nameCode = attr.getNameCode(acount); + String type = attr.getType(acount); + String value = attr.getValue(acount); + String uri = attr.getURI(acount); + String prefix = ""; + + if (localName.indexOf(':') > 0) { + prefix = localName.substring(0, localName.indexOf(':')); + localName = localName.substring(localName.indexOf(':')+1); + } + + if (uri.equals("") + && ((foStylesheet + && localName.equals("id")) + || (!foStylesheet + && (localName.equals("id") + || localName.equals("name"))))) { + // skip this attribute + } else { + newAttr.addAttribute(prefix, uri, localName, type, value); + } + } + + rtfEmitter.startElement(elem.getNameCode(), + newAttr, + elem.getNamespaces(), + elem.getNSCount()); + + elementStack.push(elem); + } + } else { + newChars[pos++] = chars[count]; + } + } + + if (pos > 0) { + rtfEmitter.characters(newChars, 0, pos); + pos = 0; + } + } + + /** + *

Add a formatted line number to the result tree fragment.

+ * + * @param lineNumber The number of the current line. + */ + protected void formatLineNumber(int lineNumber) + throws TransformerException { + + 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[] = new char[lno.length()]; + for (int count = 0; count < lno.length(); count++) { + chars[count] = lno.charAt(count); + } + + characters(chars, 0, lno.length()); + } + + /** Process end element events. */ + public void endElement(int nameCode) + throws TransformerException { + if (!elementStack.empty()) { + // if we didn't push the very first element (an fo:block or + // pre or div surrounding the whole block), then the stack will + // be empty when we get to the end of the first element... + elementStack.pop(); + } + rtfEmitter.endElement(nameCode); + } + + /** Process start element events. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) + throws TransformerException { + + if (!skipThisElement(nameCode)) { + StartElementInfo sei = new StartElementInfo(nameCode, attributes, + namespaces, nscount); + elementStack.push(sei); + } + + firstElement = false; + + rtfEmitter.startElement(nameCode, attributes, namespaces, nscount); + } + + /** + *

Protect the outer-most block wrapper.

+ * + *

Open elements in the result tree fragment are closed and reopened + * around callouts (so that callouts don't appear inside links or other + * environments). But if the result tree fragment is a single block + * (a div or pre in HTML, an fo:block in FO), that outer-most block is + * treated specially.

+ * + *

This method returns true if the element in question is that + * outermost block.

+ * + * @param nameCode The name code for the element + * + * @return True if the element is the outer-most block, false otherwise. + */ + protected boolean skipThisElement(int nameCode) { + // FIXME: This is such a gross hack... + if (firstElement) { + int thisFingerprint = namePool.getFingerprint(nameCode); + int foBlockFingerprint = namePool.getFingerprint(foURI, "block"); + int htmlPreFingerprint = namePool.getFingerprint("", "pre"); + int htmlDivFingerprint = namePool.getFingerprint("", "div"); + int xhtmlPreFingerprint = namePool.getFingerprint(xhURI, "pre"); + int xhtmlDivFingerprint = namePool.getFingerprint(xhURI, "div"); + + if ((foStylesheet && thisFingerprint == foBlockFingerprint) + || (!foStylesheet && (thisFingerprint == htmlPreFingerprint + || thisFingerprint == htmlDivFingerprint + || thisFingerprint == xhtmlPreFingerprint + || thisFingerprint == xhtmlDivFingerprint))) { + // Don't push the outer-most wrapping div, pre, or fo:block + return true; + } + } + + return false; + } + + /** + *

A private class for maintaining the information required to call + * the startElement method.

+ * + *

In order to close and reopen elements, information about those + * elements has to be maintained. This class is just the little record + * that we push on the stack to keep track of that info.

+ */ + private class StartElementInfo { + private int _nameCode; + org.xml.sax.Attributes _attributes; + int[] _namespaces; + int _nscount; + + public StartElementInfo(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) { + _nameCode = nameCode; + _attributes = attributes; + _namespaces = namespaces; + _nscount = nscount; + } + + public int getNameCode() { + return _nameCode; + } + + public org.xml.sax.Attributes getAttributes() { + return _attributes; + } + + public int[] getNamespaces() { + return _namespaces; + } + + public int getNSCount() { + return _nscount; + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/Table.java b/xsl-java/saxon65/src/com/nwalsh/saxon/Table.java new file mode 100644 index 000000000..ef23b04f7 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/Table.java @@ -0,0 +1,477 @@ +// Verbatim.java - Saxon extensions supporting DocBook verbatim environments + +package com.nwalsh.saxon; + +import java.util.Hashtable; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.Controller; +import com.icl.saxon.expr.*; +import com.icl.saxon.om.*; +import com.icl.saxon.pattern.*; +import com.icl.saxon.Context; +import com.icl.saxon.tree.*; +import com.icl.saxon.functions.Extensions; + +/** + *

Saxon extensions supporting Tables

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon + * 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 nominal table width (6in by default). */ + private static int nominalWidth = 6 * pixelsPerInch; + + /** The default table width (100% by default). */ + private static String tableWidth = "100%"; + + /** Is this an FO stylesheet? */ + private static boolean foStylesheet = false; + + /** The hash used to associate units with a length in pixels. */ + protected static Hashtable unitHash = null; + + /** + *

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; + } + + /** + *

Find the string value of a stylesheet variable or parameter

+ * + *

Returns the string value of varName in the current + * context. Returns the empty string if the variable is + * not defined.

+ * + * @param context The current stylesheet context + * @param varName The name of the variable (without the dollar sign) + * + * @return The string value of the variable + */ + protected static String getVariable(Context context, String varName) + throws TransformerException { + Value variable = null; + String varString = null; + + try { + variable = Extensions.evaluate(context, "$" + varName); + varString = variable.asString(); + return varString; + } catch (IllegalArgumentException e) { + System.out.println("Undefined variable: " + varName); + return ""; + } + } + + /** + *

Setup the parameters associated with column width calculations

+ * + *

This method queries the stylesheet for the variables + * associated with table column widths. It is called automatically before + * column widths are adjusted. The context is used to retrieve the values, + * this allows templates to redefine these variables.

+ * + *

The following variables are queried. If the variables do not + * exist, builtin defaults will be used (but you may also get a bunch + * of messages from the Java interpreter).

+ * + *
+ *
nominal.table.width
+ *
The "normal" width for tables. This must be an absolute length.
+ *
table.width
+ *
The width for tables. This may be either an absolute + * length or a percentage.
+ *
+ * + * @param context The current stylesheet context + * + */ + private static void setupColumnWidths(Context context) { + // Hardcoded defaults + nominalWidth = 6 * pixelsPerInch; + tableWidth = "100%"; + + String varString = null; + + try { + // Get the stylesheet type + varString = getVariable(context, "stylesheet.result.type"); + foStylesheet = varString.equals("fo"); + + // Get the nominal table width + varString = getVariable(context, "nominal.table.width"); + nominalWidth = convertLength(varString); + + // Get the table width + varString = getVariable(context, "table.width"); + tableWidth = varString; + } catch (TransformerException e) { + //nop, can't happen + } + } + + /** + *

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 Saxon + * @param rtf_ns The result tree fragment containing the colgroup. + * + * @return The result tree fragment containing the adjusted colgroup. + * + */ + public static NodeSetValue adjustColumnWidths (Context context, + NodeSetValue rtf_ns) { + + FragmentValue rtf = (FragmentValue) rtf_ns; + + setupColumnWidths(context); + + try { + Controller controller = context.getController(); + NamePool namePool = controller.getNamePool(); + + ColumnScanEmitter csEmitter = new ColumnScanEmitter(namePool); + rtf.replay(csEmitter); + + int numColumns = csEmitter.columnCount(); + String widths[] = csEmitter.columnWidths(); + + 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); + } + } + + ColumnUpdateEmitter cuEmitter = new ColumnUpdateEmitter(controller, + namePool, + widths); + + rtf.replay(cuEmitter); + return cuEmitter.getResultTreeFragment(); + } catch (TransformerException e) { + // This "can't" happen. + System.out.println("Transformer Exception in adjustColumnWidths"); + return rtf; + } + } + + /** + * Correct rounding errors introduced in calculating the width of each + * column. Make sure they sum to 100% in the end. + */ + protected static 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-java/saxon65/src/com/nwalsh/saxon/Text.java b/xsl-java/saxon65/src/com/nwalsh/saxon/Text.java new file mode 100644 index 000000000..71ef56824 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/Text.java @@ -0,0 +1,203 @@ +// Text - Saxon extension element for inserting text + +package com.nwalsh.saxon; + +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 javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.URIResolver; +import javax.xml.transform.Source; + +import com.icl.saxon.Context; +import com.icl.saxon.style.StyleElement; +import com.icl.saxon.output.Outputter; +import com.icl.saxon.expr.Expression; + +import org.xml.sax.AttributeList; + +/** + *

Saxon extension element for inserting text + * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon + * 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 extends StyleElement { + /** + *

Constructor for Text

+ * + *

Does nothing.

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

Is this element an instruction?

+ * + *

Yes, it is.

+ * + * @return true + */ + public boolean isInstruction() { + return true; + } + + /** + *

Can this element contain a template-body?

+ * + *

Yes, it can, but only so that it can contain xsl:fallback.

+ * + * @return true + */ + public boolean mayContainTemplateBody() { + return true; + } + + /** + *

Validate the arguments

+ * + *

The element must have an href attribute.

+ */ + public void prepareAttributes() throws TransformerConfigurationException { + // Get mandatory href attribute + String fnAtt = getAttribute("href"); + if (fnAtt == null) { + reportAbsence("href"); + } + } + + /** Validate that the element occurs in a reasonable place. */ + public void validate() throws TransformerConfigurationException { + checkWithinTemplate(); + } + + /** + *

Insert the text of the file into the result tree

+ * + *

Processing this element inserts the contents of the URL named + * by the href attribute into the result tree as plain text.

+ * + *

Optional encoding attribute can specify encoding of resource. + * If not specified default system encoding is used.

+ * + */ + public void process( Context context ) throws TransformerException { + Outputter out = context.getOutputter(); + + String hrefAtt = getAttribute("href"); + Expression hrefExpr = makeAttributeValueTemplate(hrefAtt); + String href = hrefExpr.evaluateAsString(context); + + String encodingAtt = getAttribute("encoding"); + Expression encodingExpr = makeAttributeValueTemplate(encodingAtt); + String encoding = encodingExpr.evaluateAsString(context); + + String baseURI = context.getContextNodeInfo().getBaseURI(); + + URIResolver resolver = context.getController().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 e0) { + // what the!? + baseURL = null; + } + + 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++; + } + out.writeContent(nchars, 0, nlen); + // handle look aheaded character + if (carry != -1) out.writeContent(String.valueOf((char)carry)); + carry = -1; + } + is.close(); + } catch (Exception e) { + System.out.println("Cannot read " + href); + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/TextFactory.java b/xsl-java/saxon65/src/com/nwalsh/saxon/TextFactory.java new file mode 100644 index 000000000..6dc09672e --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/TextFactory.java @@ -0,0 +1,67 @@ +// TextFactory - Saxon extension element factory + +package com.nwalsh.saxon; + +import com.icl.saxon.style.ExtensionElementFactory; +import org.xml.sax.SAXException; + +/** + *

Saxon extension element factory + * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

This class provides a + * Saxon + * extension element factory for the Text extension element + * family.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

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

Constructor for TextFactory

+ * + *

Does nothing.

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

Return the class that implements a particular extension element.

+ * + * @param localname The local name of the extension element. + * + * @return The class that handles that extension element. + * + * @exception SAXException("Unknown Text extension element") + */ + public Class getExtensionClass(String localname) { + if (localname.equals("insertfile")) { + try { + return Class.forName("com.nwalsh.saxon.Text"); + } catch (ClassNotFoundException e) { + return null; + } + } + return null; + } +} + + + + + diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinks.java b/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinks.java new file mode 100644 index 000000000..18b6c5e15 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinks.java @@ -0,0 +1,132 @@ +// UnwrapLinks.java - Saxon extension for unwrapping nested links + +package com.nwalsh.saxon; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.Controller; +import com.icl.saxon.expr.*; +import com.icl.saxon.om.*; +import com.icl.saxon.pattern.*; +import com.icl.saxon.Context; +import com.icl.saxon.tree.*; +import com.icl.saxon.functions.Extensions; +import com.nwalsh.saxon.UnwrapLinksEmitter; + +/** + *

Saxon extension for unwrapping nested links

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2002 Norman Walsh.

+ * + *

This class provides a + * Saxon 6.* + * implementation of a link unwrapper.

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class UnwrapLinks { + /** True if the stylesheet is producing formatting objects */ + private static boolean foStylesheet = false; + + /** + *

Constructor for UnwrapLinks

+ * + *

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

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

Find the string value of a stylesheet variable or parameter

+ * + *

Returns the string value of varName in the current + * context. Returns the empty string if the variable is + * not defined.

+ * + * @param context The current stylesheet context + * @param varName The name of the variable (without the dollar sign) + * + * @return The string value of the variable + */ + protected static String getVariable(Context context, String varName) { + Value variable = null; + String varString = null; + + try { + variable = Extensions.evaluate(context, "$" + varName); + varString = variable.asString(); + return varString; + } catch (TransformerException te) { + System.out.println("Undefined variable: " + varName); + return ""; + } catch (IllegalArgumentException iae) { + System.out.println("Undefined variable: " + varName); + return ""; + } + } + + /** + *

Setup the parameters associated with unwrapping links

+ * + * @param context The current stylesheet context + * + */ + private static void setupUnwrapLinks(Context context) { + // Get the stylesheet type + String varString = getVariable(context, "stylesheet.result.type"); + foStylesheet = (varString.equals("fo")); + } + + /** + *

Unwrap links

+ * + * @param context The current stylesheet context. + * @param rtf_ns The result tree fragment of the verbatim environment. + * + * @return The modified result tree fragment. + */ + public static NodeSetValue unwrapLinks (Context context, + NodeSetValue rtf_ns) { + + FragmentValue rtf = (FragmentValue) rtf_ns; + boolean tryAgain = true; + + setupUnwrapLinks(context); + + try { + Controller controller = context.getController(); + NamePool namePool = controller.getNamePool(); + + while (tryAgain) { + UnwrapLinksEmitter ulEmitter = new UnwrapLinksEmitter(controller, + namePool, + foStylesheet); + rtf.replay(ulEmitter); + tryAgain = ulEmitter.tryAgain(); + rtf = (FragmentValue) ulEmitter.getResultTreeFragment(); + } + + return rtf; + + } catch (TransformerException e) { + // This "can't" happen. + System.out.println("Transformer Exception in unwrapLinks"); + return rtf; + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinksEmitter.java b/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinksEmitter.java new file mode 100644 index 000000000..582807264 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/UnwrapLinksEmitter.java @@ -0,0 +1,233 @@ +package com.nwalsh.saxon; + +import java.util.Stack; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.output.*; +import com.icl.saxon.om.*; +import com.icl.saxon.Controller; +import com.icl.saxon.tree.AttributeCollection; + +/** + *

Saxon extension to unwrap links in a result tree fragment.

+ * + *

$Id$

+ * + *

Copyright (C) 2000, 2002 Norman Walsh.

+ * + *

This class provides the guts of a + * Saxon 6.* + * implementation of a link unwrapper.

+ * + *

The general design is this: the stylesheets construct a result tree + * fragment for some environment. Then the result tree fragment + * is "replayed" through the UnwrapLinksEmitter; the UnwrapLinksEmitter + * builds a + * new result tree fragment from this event stream with top-level links unwrapped. + * That RTF is returned. Note that only a single level of unwrapping + * is performed. This is clearly a crude implementation. + *

+ * + *

Change Log:

+ *
+ *
1.0
+ *

Initial release.

+ *
+ * + * @author Norman Walsh + * ndw@nwalsh.com + * + * @version $Id$ + * + */ +public class UnwrapLinksEmitter extends CopyEmitter { + /** A stack for the preserving information about open elements. */ + protected Stack elementStack = null; + protected Stack saveStack = null; + + /** The FO namespace name. */ + protected static String foURI = "http://www.w3.org/1999/XSL/Format"; + + /** The XHTML namespace name. */ + protected static String xhURI = "http://www.w3.org/1999/xhtml"; + + /** Is the stylesheet currently running an FO stylesheet? */ + protected boolean foStylesheet = false; + + /** Are we currently in a link? How deep? */ + protected int linkDepth = 0; + protected int skipDepth = 0; + + protected int htmlAFingerprint = 0; + protected int xhtmlAFingerprint = 0; + protected boolean inSkip = false; + protected boolean tryAgain = false; + + + /**

Constructor for the UnwrapLinksEmitter.

+ * + * @param namePool The name pool to use for constructing elements and attributes. + * @param foStylesheet Is this an FO stylesheet? + */ + public UnwrapLinksEmitter(Controller controller, + NamePool namePool, + boolean foStylesheet) { + super(controller,namePool); + elementStack = new Stack(); + this.foStylesheet = foStylesheet; + + htmlAFingerprint = namePool.getFingerprint("", "a"); + xhtmlAFingerprint = namePool.getFingerprint(xhURI, "a"); + } + + /** Process start element events. */ + public void startElement(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) + throws TransformerException { + + int thisFingerprint = namePool.getFingerprint(nameCode); + boolean isLink = (thisFingerprint == htmlAFingerprint + || thisFingerprint == xhtmlAFingerprint); + + if (isLink) { + linkDepth++; + tryAgain = tryAgain || inSkip; + } + + if (isLink && linkDepth > 1 && !inSkip) { + inSkip = true; + + // Close all the open elements + saveStack = new Stack(); + Stack tempStack = new Stack(); + while (!elementStack.empty()) { + StartElementInfo elem = (StartElementInfo) elementStack.pop(); + rtfEmitter.endElement(elem.getNameCode()); + saveStack.push(elem); + tempStack.push(elem); + } + + while (!tempStack.empty()) { + StartElementInfo elem = (StartElementInfo) tempStack.pop(); + elementStack.push(elem); + } + } + + if (inSkip) { + skipDepth++; + } else { + } + + rtfEmitter.startElement(nameCode,attributes,namespaces,nscount); + + StartElementInfo sei = new StartElementInfo(nameCode, attributes, + namespaces, nscount); + elementStack.push(sei); + } + + /** Process end element events. */ + public void endElement(int nameCode) throws TransformerException { + int thisFingerprint = namePool.getFingerprint(nameCode); + boolean isLink = (thisFingerprint == htmlAFingerprint + || thisFingerprint == xhtmlAFingerprint); + + rtfEmitter.endElement(nameCode); + elementStack.pop(); + + if (isLink) { + linkDepth--; + } + + if (inSkip) { + skipDepth--; + inSkip = (skipDepth > 0); + if (!inSkip) { + // Reopen all the ones we closed before... + while (!saveStack.empty()) { + StartElementInfo elem = (StartElementInfo) saveStack.pop(); + + AttributeCollection attr = (AttributeCollection)elem.getAttributes(); + AttributeCollection newAttr = new AttributeCollection(namePool); + + for (int acount = 0; acount < attr.getLength(); acount++) { + String localName = attr.getLocalName(acount); + String type = attr.getType(acount); + String value = attr.getValue(acount); + String uri = attr.getURI(acount); + String prefix = ""; + + if (localName.indexOf(':') > 0) { + prefix = localName.substring(0, localName.indexOf(':')); + localName = localName.substring(localName.indexOf(':')+1); + } + + if (uri.equals("") + && ((foStylesheet + && localName.equals("id")) + || (!foStylesheet + && (localName.equals("id") + || localName.equals("name"))))) { + // skip this attribute + } else { + newAttr.addAttribute(prefix, uri, localName, type, value); + } + } + + rtfEmitter.startElement(elem.getNameCode(), + newAttr, + elem.getNamespaces(), + elem.getNSCount()); + } + } + } + } + + public boolean tryAgain() + throws TransformerException { + return tryAgain; + } + + /** + *

A private class for maintaining the information required to call + * the startElement method.

+ * + *

In order to close and reopen elements, information about those + * elements has to be maintained. This class is just the little record + * that we push on the stack to keep track of that info.

+ */ + private class StartElementInfo { + private int _nameCode; + org.xml.sax.Attributes _attributes; + int[] _namespaces; + int _nscount; + + public StartElementInfo(int nameCode, + org.xml.sax.Attributes attributes, + int[] namespaces, + int nscount) { + _nameCode = nameCode; + _attributes = attributes; + _namespaces = namespaces; + _nscount = nscount; + } + + public int getNameCode() { + return _nameCode; + } + + public org.xml.sax.Attributes getAttributes() { + return _attributes; + } + + public int[] getNamespaces() { + return _namespaces; + } + + public int getNSCount() { + return _nscount; + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/Verbatim.java b/xsl-java/saxon65/src/com/nwalsh/saxon/Verbatim.java new file mode 100644 index 000000000..2bff9a02e --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/Verbatim.java @@ -0,0 +1,491 @@ +// Verbatim.java - Saxon extensions supporting DocBook verbatim environments + +package com.nwalsh.saxon; + +import java.util.Stack; +import java.util.StringTokenizer; +import org.xml.sax.*; +import org.w3c.dom.*; +import javax.xml.transform.TransformerException; +import com.icl.saxon.Controller; +import com.icl.saxon.expr.*; +import com.icl.saxon.om.*; +import com.icl.saxon.pattern.*; +import com.icl.saxon.Context; +import com.icl.saxon.tree.*; +import com.icl.saxon.functions.Extensions; +import com.nwalsh.saxon.NumberLinesEmitter; +import com.nwalsh.saxon.CalloutEmitter; + +/** + *

Saxon extensions supporting DocBook verbatim environments

+ * + *

$Id$

+ * + *

Copyright (C) 2000 Norman Walsh.

+ * + *

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

+ * + *

Line Numbering

+ *

The numberLines method 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 method 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 { + /** True if the stylesheet is producing formatting objects */ + private static boolean foStylesheet = false; + /** The modulus for line numbering (every 'modulus' line is numbered). */ + private static int modulus = 0; + /** The width (in characters) of line numbers (for padding). */ + private static int width = 0; + /** The starting line number. */ + private static int startinglinenumber = 1; + /** The separator between the line number and the verbatim text. */ + private static String separator = ""; + + /** True if callouts have been setup */ + private static boolean calloutsSetup = false; + /** The default column for callouts that have only a line or line range */ + private static int defaultColumn = 60; + /** The path to use for graphical callout decorations. */ + private static String graphicsPath = null; + /** The extension to use for graphical callout decorations. */ + private static String graphicsExt = null; + /** The largest callout number that can be represented graphically. */ + private static int graphicsMax = 10; + + /** The FormatCallout object to use for formatting callouts. */ + private static FormatCallout fCallout = null; + + /** + *

Constructor for Verbatim

+ * + *

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

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

Find the string value of a stylesheet variable or parameter

+ * + *

Returns the string value of varName in the current + * context. Returns the empty string if the variable is + * not defined.

+ * + * @param context The current stylesheet context + * @param varName The name of the variable (without the dollar sign) + * + * @return The string value of the variable + */ + protected static String getVariable(Context context, String varName) { + Value variable = null; + String varString = null; + + try { + variable = Extensions.evaluate(context, "$" + varName); + varString = variable.asString(); + return varString; + } catch (TransformerException te) { + System.out.println("Undefined variable: " + varName); + return ""; + } catch (IllegalArgumentException iae) { + System.out.println("Undefined variable: " + varName); + return ""; + } + } + + /** + *

Setup the parameters associated with line numbering

+ * + *

This method queries the stylesheet for the variables + * associated with line numbering. It is called automatically before + * lines are numbered. The context is used to retrieve the values, + * this allows templates to redefine these variables.

+ * + *

The following variables are queried. If the variables do not + * exist, builtin defaults will be used (but you may also get a bunch + * of messages from the Java interpreter).

+ * + *
+ *
linenumbering.everyNth
+ *
Specifies the lines that will be numbered. The first line is + * always numbered. (builtin default: 5).
+ *
linenumbering.width
+ *
Specifies the width of the numbers. If the specified width is too + * narrow for the largest number needed, it will automatically be made + * wider. (builtin default: 3).
+ *
linenumbering.separator
+ *
Specifies the string that separates line numbers from lines + * in the program listing. (builtin default: " ").
+ *
linenumbering.startinglinenumber
+ *
Specifies the initial line number + * in the program listing. (builtin default: "1").
+ *
stylesheet.result.type
+ *
Specifies the stylesheet result type. The value is either 'fo' + * (for XSL Formatting Objects) or it isn't. (builtin default: html).
+ *
+ * + * @param context The current stylesheet context + * + */ + private static void setupLineNumbering(Context context) { + // Hardcoded defaults + modulus = 5; + width = 3; + startinglinenumber = 1; + separator = " "; + foStylesheet = false; + + String varString = null; + + // Get the modulus + varString = getVariable(context, "linenumbering.everyNth"); + try { + modulus = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$linenumbering.everyNth is not a number: " + varString); + } + + // Get the width + varString = getVariable(context, "linenumbering.width"); + try { + width = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$linenumbering.width is not a number: " + varString); + } + + // Get the startinglinenumber + varString = getVariable(context, "linenumbering.startinglinenumber"); + try { + startinglinenumber = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$linenumbering.startinglinenumber is not a number: " + varString); + } + + // Get the separator + varString = getVariable(context, "linenumbering.separator"); + separator = varString; + + // Get the stylesheet type + varString = getVariable(context, "stylesheet.result.type"); + foStylesheet = (varString.equals("fo")); + } + + /** + *

Number lines in a verbatim environment

+ * + *

The extension function expects the following variables to be + * available in the calling context: $linenumbering.everyNth, + * $linenumbering.width, $linenumbering.separator, and + * $stylesheet.result.type.

+ * + *

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 'everyNth' line + * is numbered (so if everyNth=5, lines 1, 5, 10, 15, etc. will be + * numbered. If there are fewer than everyNth lines in the environment, + * every line is numbered.

+ * + *

Every line number will be right justified in a string 'width' + * 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.)

+ * + *

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

+ * + *

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 if the stylesheet.result.type is "html" and + * 'id' attributes will not be duplicated if the result type is "fo".

+ * + * @param context The current stylesheet context. + * @param rtf_ns The result tree fragment of the verbatim environment. + * + * @return The modified result tree fragment. + */ + public static NodeSetValue numberLines (Context context, + NodeSetValue rtf_ns) { + + FragmentValue rtf = (FragmentValue) rtf_ns; + + setupLineNumbering(context); + + try { + LineCountEmitter lcEmitter = new LineCountEmitter(); + rtf.replay(lcEmitter); + int numLines = lcEmitter.lineCount(); + + int listingModulus = numLines < modulus ? 1 : modulus; + + double log10numLines = Math.log(numLines) / Math.log(10); + + int listingWidth = width < log10numLines+1 + ? (int) Math.floor(log10numLines + 1) + : width; + + Controller controller = context.getController(); + NamePool namePool = controller.getNamePool(); + NumberLinesEmitter nlEmitter = new NumberLinesEmitter(controller, + namePool, + startinglinenumber, + listingModulus, + listingWidth, + separator, + foStylesheet); + rtf.replay(nlEmitter); + return nlEmitter.getResultTreeFragment(); + } catch (TransformerException e) { + // This "can't" happen. + System.out.println("Transformer Exception in numberLines"); + return rtf; + } + } + + /** + *

Setup the parameters associated with callouts

+ * + *

This method queries the stylesheet for the variables + * associated with line numbering. It is called automatically before + * callouts are processed. The context is used to retrieve the values, + * this allows templates to redefine these variables.

+ * + *

The following variables are queried. If the variables do not + * exist, builtin defaults will be used (but you may also get a bunch + * of messages from the Java interpreter).

+ * + *
+ *
callout.graphics
+ *
Are we using callout graphics? A value of 0 or "" is false, + * any other value is true. If callout graphics are not used, the + * parameters related to graphis are not queried.
+ *
callout.graphics.path
+ *
Specifies the path to callout graphics.
+ *
callout.graphics.extension
+ *
Specifies the extension ot use for callout graphics.
+ *
callout.graphics.number.limit
+ *
Identifies the largest number that can be represented as a + * graphic. Larger callout numbers will be represented using text.
+ *
callout.defaultcolumn
+ *
Specifies the default column for callout bullets that do not + * specify a column.
+ *
stylesheet.result.type
+ *
Specifies the stylesheet result type. The value is either 'fo' + * (for XSL Formatting Objects) or it isn't. (builtin default: html).
+ *
+ * + * @param context The current stylesheet context + * + */ + private static void setupCallouts(Context context) { + NamePool namePool = context.getController().getNamePool(); + + boolean useGraphics = false; + boolean useUnicode = false; + + int unicodeStart = 49; + int unicodeMax = 0; + + String unicodeFont = ""; + + // Hardcoded defaults + defaultColumn = 60; + graphicsPath = null; + graphicsExt = null; + graphicsMax = 0; + foStylesheet = false; + calloutsSetup = true; + + Value variable = null; + String varString = null; + + // Get the stylesheet type + varString = getVariable(context, "stylesheet.result.type"); + foStylesheet = (varString.equals("fo")); + + // Get the default column + varString = getVariable(context, "callout.defaultcolumn"); + try { + defaultColumn = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$callout.defaultcolumn is not a number: " + + varString); + } + + // Use graphics at all? + varString = getVariable(context, "callout.graphics"); + useGraphics = !(varString.equals("0") || varString.equals("")); + + // Use unicode at all? + varString = getVariable(context, "callout.unicode"); + useUnicode = !(varString.equals("0") || varString.equals("")); + + if (useGraphics) { + // Get the graphics path + varString = getVariable(context, "callout.graphics.path"); + graphicsPath = varString; + + // Get the graphics extension + varString = getVariable(context, "callout.graphics.extension"); + graphicsExt = varString; + + // Get the number limit + varString = getVariable(context, "callout.graphics.number.limit"); + try { + graphicsMax = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$callout.graphics.number.limit is not a number: " + + varString); + graphicsMax = 0; + } + + fCallout = new FormatGraphicCallout(namePool, + graphicsPath, + graphicsExt, + graphicsMax, + foStylesheet); + } else if (useUnicode) { + // Get the starting character + varString = getVariable(context, "callout.unicode.start.character"); + try { + unicodeStart = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$callout.unicode.start.character is not a number: " + + varString); + unicodeStart = 48; + } + + // Get the number limit + varString = getVariable(context, "callout.unicode.number.limit"); + try { + unicodeMax = Integer.parseInt(varString); + } catch (NumberFormatException nfe) { + System.out.println("$callout.unicode.number.limit is not a number: " + + varString); + unicodeStart = 0; + } + + // Get the font + unicodeFont = getVariable(context, "callout.unicode.font"); + if (unicodeFont == null) { + unicodeFont = ""; + } + + fCallout = new FormatUnicodeCallout(namePool, + unicodeFont, + unicodeStart, + unicodeMax, + foStylesheet); + } else { + fCallout = new FormatTextCallout(namePool, foStylesheet); + } + } + + /** + *

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. Callout numbers may + * also be represented as graphics. 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.

+ * + *

If graphical callouts are used, and the callout number is less + * than or equal to the $callout.graphics.number.limit, the following image + * will be generated for HTML: + * + *

+   * <img src="$callout.graphics.path/999$callout.graphics.ext"
+   *         alt="conumber">
+   * 
+ * + * If the $stylesheet.result.type is 'fo', the following image will + * be generated: + * + *
+   * <fo:external-graphic src="$callout.graphics.path/999$callout.graphics.ext"/>
+   * 
+ * + *

If the callout number exceeds $callout.graphics.number.limit, + * the callout will be the callout number surrounded by + * parenthesis.

+ * + * @param context The stylesheet context. + * @param areaspecNodeList The source node set that contains the areaspec. + * @param rtf_ns The result tree fragment of the verbatim environment. + * + * @return The modified result tree fragment. + */ + public static NodeSetValue insertCallouts (Context context, + NodeList areaspecNodeList, + NodeSetValue rtf_ns) { + + FragmentValue rtf = (FragmentValue) rtf_ns; + + setupCallouts(context); + + try { + Controller controller = context.getController(); + NamePool namePool = controller.getNamePool(); + CalloutEmitter cEmitter = new CalloutEmitter(controller, + namePool, + defaultColumn, + foStylesheet, + fCallout); + cEmitter.setupCallouts(areaspecNodeList); + rtf.replay(cEmitter); + return cEmitter.getResultTreeFragment(); + } catch (TransformerException e) { + // This "can't" happen. + System.out.println("Transformer Exception in insertCallouts"); + return rtf; + } + } +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/Windows1252.java b/xsl-java/saxon65/src/com/nwalsh/saxon/Windows1252.java new file mode 100644 index 000000000..23c437329 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/Windows1252.java @@ -0,0 +1,110 @@ +/* This file was kindly provided by Sectra AB, Sweden to DocBook community */ +package com.nwalsh.saxon; + +import com.icl.saxon.charcode.PluggableCharacterSet; + +/** + * + * $Id$ + * + * File: Windows1252CharacterSet.java + * Created: May 26 2004 + * Author: Pontus Haglund + * Project: Venus + * + * This class extends Saxon 6.5.x with the windows-1252 character set. + * + * It is particularly useful when generating HTML Help for + * Western European Languages. + * + * To use this class for generating HTML Help output with the + * DocBook XSL stylesheets, complete the following steps; + * + * 1. Make sure that the Saxon 6.5.x jar file and the jar file for + * the DocBook XSL Java extensions are in your CLASSPATH + * + * 2. Create a DocBook XSL customization layer -- a file named + * "mystylesheet.xsl" or whatever -- that, at a minimum, + * contains the following: + * + * + * + * + * + * + * + * + * + * 3. Invoke Saxon with the "encoding.windows-1252" Java system + * property set to "com.nwalsh.saxon.Windows1252"; for example: + * + * java \ + * -Dencoding.windows-1252=com.nwalsh.saxon.Windows1252 \ + * com.icl.saxon.StyleSheet \ + * mydoc.xml mystylesheet.xsl + * + * Or, for a more complete "real world" case showing other + * options you'll typically want to use: + * + * java \ + * -Dencoding.windows-1252=com.nwalsh.saxon.Windows1252 \ + * -Djavax.xml.parsers.DocumentBuilderFactory=org.apache.xerces.jaxp.DocumentBuilderFactoryImpl \ + * -Djavax.xml.parsers.SAXParserFactory=org.apache.xerces.jaxp.SAXParserFactoryImpl \ + * -Djavax.xml.transform.TransformerFactory=com.icl.saxon.TransformerFactoryImpl \ + * com.icl.saxon.StyleSheet \ + * -x org.apache.xml.resolver.tools.ResolvingXMLReader \ + * -y org.apache.xml.resolver.tools.ResolvingXMLReader \ + * -r org.apache.xml.resolver.tools.CatalogResolver \ + * mydoc.xml mystylesheet.xsl + * + * In both cases, the "mystylesheet.xsl" file should be a DocBook + * customization layer containing the parameters show in step 2. + * + */ + + + +public class Windows1252 implements PluggableCharacterSet { + + public final boolean inCharset(int c) { + + return (c >= 0x00 && c <= 0x7F) || + (c >= 0xA0 && c <= 0xFF) || + (c == 0x20AC) || + (c == 0x201A) || + (c == 0x0192) || + (c == 0x201E) || + (c == 0x2026) || + (c == 0x2020) || + (c == 0x2021) || + (c == 0x02C6) || + (c == 0x2030) || + (c == 0x0160) || + (c == 0x2039) || + (c == 0x0152) || + (c == 0x017D) || + (c == 0x2018) || + (c == 0x2019) || + (c == 0x201C) || + (c == 0x201D) || + (c == 0x2022) || + (c == 0x2013) || + (c == 0x2014) || + (c == 0x02DC) || + (c == 0x2122) || + (c == 0x0161) || + (c == 0x203A) || + (c == 0x0153) || + (c == 0x017E) || + (c == 0x0178); + + + } + + public String getEncodingName() { + return "WINDOWS-1252"; + } + +} diff --git a/xsl-java/saxon65/src/com/nwalsh/saxon/package.html b/xsl-java/saxon65/src/com/nwalsh/saxon/package.html new file mode 100644 index 000000000..b05a46709 --- /dev/null +++ b/xsl-java/saxon65/src/com/nwalsh/saxon/package.html @@ -0,0 +1,48 @@ + + +Norman Walsh's Saxon Extensions Package + + +

Norman Walsh's Saxon Extensions Package for Saxon 6.*

+ +

This package implements Saxon extensions for XSLT.

+ +

Copyright (C) 2000 Norman Walsh

+

Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software.

+ +

Except as contained in this notice, the names of individuals +credited with contribution to this software shall not be used in +advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from the +individuals in question.

+ +

Anything derived from this Software that is publically +distributed will be identified with a different name and the +version strings in any derived Software will be changed so that no +possibility of confusion between the derived package and this +Software will exist.

+ + +
+

Warranty

+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL NORMAN WALSH OR ANY OTHER +CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE.

+
+ + + diff --git a/xsl-java/xalan27/build.xml b/xsl-java/xalan27/build.xml new file mode 100644 index 000000000..c39733b62 --- /dev/null +++ b/xsl-java/xalan27/build.xml @@ -0,0 +1,69 @@ + + + + + + Builds, tests, and runs the project xalan27. + + + diff --git a/xsl-java/xalan27/nbproject/.cvsignore b/xsl-java/xalan27/nbproject/.cvsignore new file mode 100644 index 000000000..3e18ebf09 --- /dev/null +++ b/xsl-java/xalan27/nbproject/.cvsignore @@ -0,0 +1 @@ +private diff --git a/xsl-java/xalan27/nbproject/build-impl.xml b/xsl-java/xalan27/nbproject/build-impl.xml new file mode 100644 index 000000000..fa2628e4d --- /dev/null +++ b/xsl-java/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-java/xalan27/nbproject/genfiles.properties b/xsl-java/xalan27/nbproject/genfiles.properties new file mode 100644 index 000000000..fd61ced98 --- /dev/null +++ b/xsl-java/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-java/xalan27/nbproject/project.properties b/xsl-java/xalan27/nbproject/project.properties new file mode 100644 index 000000000..f6e21137a --- /dev/null +++ b/xsl-java/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-java/xalan27/nbproject/project.xml b/xsl-java/xalan27/nbproject/project.xml new file mode 100644 index 000000000..687ff026d --- /dev/null +++ b/xsl-java/xalan27/nbproject/project.xml @@ -0,0 +1,16 @@ + + + org.netbeans.modules.java.j2seproject + + + xalan27 + 1.6.5 + + + + + + + + + diff --git a/xsl-java/xalan27/src/com/nwalsh/xalan/CVS.java b/xsl-java/xalan27/src/com/nwalsh/xalan/CVS.java new file mode 100644 index 000000000..fbd190e90 --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/Callout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Callout.java new file mode 100644 index 000000000..920cfc3bf --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/FormatCallout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/FormatCallout.java new file mode 100644 index 000000000..ff3018b95 --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/FormatDingbatCallout.java new file mode 100644 index 000000000..e78d3cc1d --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/FormatGraphicCallout.java new file mode 100644 index 000000000..6686b36b9 --- /dev/null +++ b/xsl-java/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", "url(" + + graphicsPath + num + graphicsExt + ")"); + + } 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-java/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/FormatTextCallout.java new file mode 100644 index 000000000..8cdbef541 --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java b/xsl-java/xalan27/src/com/nwalsh/xalan/FormatUnicodeCallout.java new file mode 100644 index 000000000..2fdaf54ce --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/Func.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Func.java new file mode 100644 index 000000000..771bd13ac --- /dev/null +++ b/xsl-java/xalan27/src/com/nwalsh/xalan/Func.java @@ -0,0 +1,58 @@ +// Func - Xalann extension function test + +package com.nwalsh.xalan; + +import org.xml.sax.SAXException; +import org.xml.sax.ContentHandler; + +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.traversal.NodeIterator; + +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-java/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java b/xsl-java/xalan27/src/com/nwalsh/xalan/ImageIntrinsics.java new file mode 100644 index 000000000..94f02f4c0 --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/Params.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Params.java new file mode 100644 index 000000000..20261261d --- /dev/null +++ b/xsl-java/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-java/xalan27/src/com/nwalsh/xalan/Table.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Table.java new file mode 100644 index 000000000..f74fc93a5 --- /dev/null +++ b/xsl-java/xalan27/src/com/nwalsh/xalan/Table.java @@ -0,0 +1,538 @@ +// Verbatim.java - Xalan extensions supporting DocBook verbatim environments + +package com.nwalsh.xalan; + +import java.util.Hashtable; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +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.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-java/xalan27/src/com/nwalsh/xalan/Text.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Text.java new file mode 100644 index 000000000..345d5b121 --- /dev/null +++ b/xsl-java/xalan27/src/com/nwalsh/xalan/Text.java @@ -0,0 +1,201 @@ +// 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.ContentHandler; + +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-java/xalan27/src/com/nwalsh/xalan/Verbatim.java b/xsl-java/xalan27/src/com/nwalsh/xalan/Verbatim.java new file mode 100644 index 000000000..4ab456a15 --- /dev/null +++ b/xsl-java/xalan27/src/com/nwalsh/xalan/Verbatim.java @@ -0,0 +1,931 @@ +// Verbatim.java - Xalan extensions supporting DocBook verbatim environments + +package com.nwalsh.xalan; + +import java.util.Stack; +import java.util.StringTokenizer; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Element; +import org.w3c.dom.traversal.NodeIterator; + +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; +import org.xml.sax.helpers.AttributesImpl; + +/** + *

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; + } +} diff --git a/xsl-java/xsltproc/python/.cvsignore b/xsl-java/xsltproc/python/.cvsignore new file mode 100644 index 000000000..68e5bd57e --- /dev/null +++ b/xsl-java/xsltproc/python/.cvsignore @@ -0,0 +1,2 @@ +cwtest.xml cwtest.xsl +*.pyc diff --git a/xsl-java/xsltproc/python/README b/xsl-java/xsltproc/python/README new file mode 100644 index 000000000..a7cd35678 --- /dev/null +++ b/xsl-java/xsltproc/python/README @@ -0,0 +1,3 @@ +THIS IS A WORK IN PROGRESS + +IN PARTICULAR: IT DOES NOT WORK NOW! diff --git a/xsl-java/xsltproc/python/docbook.py b/xsl-java/xsltproc/python/docbook.py new file mode 100644 index 000000000..f82e07a58 --- /dev/null +++ b/xsl-java/xsltproc/python/docbook.py @@ -0,0 +1,240 @@ +#!/usr/bin/python -u + +# THIS IS ALPHA CODE AND IS NOT COMPLETE! + +import sys +import string +import libxml2 +import libxslt +import re +import math + +# Some globals +pixelsPerInch = 96.0 +unitHash = { 'in': pixelsPerInch, + 'cm': pixelsPerInch / 2.54, + 'mm': pixelsPerInch / 25.4, + 'pc': (pixelsPerInch / 72.0) * 12, + 'pt': pixelsPerInch / 72.0, + 'px': 1 } + +# ====================================================================== + +def adjustColumnWidths(ctx, nodeset): + # + # Small check to verify the context is correcly accessed + # + try: + pctxt = libxslt.xpathParserContext(_obj=ctx) + ctxt = pctxt.context() + tctxt = ctxt.transformContext() + except: + pass + + # Get the nominal table width + varString = lookupVariable(tctxt, "nominal.table.width", None); + if varString == None: + nominalWidth = 6 * pixelsPerInch; + else: + nominalWidth = convertLength(varString); + + # Get the requested table width + tableWidth = lookupVariable(tctxt, "table.width", "100%") + + foStylesheet = (tctxt.variableLookup("stylesheet.result.type", None) == "fo"); + + relTotal = 0 + relParts = [] + + absTotal = 0 + absParts = [] + + colgroup = libxml2.xmlNode(_obj = nodeset[0]) + # If this is an foStylesheet, we've been passed a list of fo:table-columns. + # Otherwise we've been passed a colgroup that contains a list of cols. + if foStylesheet: + colChildren = colgroup + else: + colChildren = colgroup.children + + col = colChildren + while col != None: + if foStylesheet: + width = col.prop("column-width") + else: + width = col.prop("width") + + if width == None: + width = "1*" + + relPart = 0.0; + absPart = 0.0; + starPos = string.find(width, "*"); + if starPos >= 0: + relPart, absPart = string.split(width, "*", 2) + relPart = float(relPart); + relTotal = relTotal + float(relPart) + else: + absPart = width + + pixels = convertLength(absPart); + absTotal = absTotal + pixels; + + relParts.append(relPart); + absParts.append(pixels); + + col = col.next + + # 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. + + widths = []; + + if relTotal == 0: + for absPart in absParts: + if foStylesheet: + inches = absPart / pixelsPerInch + widths.append("%4.2fin" % inches) + else: + widths.append("%d" % absPart) + elif absTotal == 0: + for relPart in relParts: + rel = relPart / relTotal * 100; + widths.append(rel); + widths = correctRoundingError(widths) + else: + pixelWidth = nominalWidth; + if string.find(tableWidth, "%") < 0: + pixelWidth = convertLength(tableWidth); + + if pixelWidth <= absTotal: + print "Table is wider than table width" + else: + pixelWidth = pixelWidth - absTotal; + + absTotal = 0 + for count in range(len(relParts)): + rel = relParts[count] / relTotal * pixelWidth + relParts[count] = rel + absParts[count] + absTotal = absTotal + rel + absParts[count] + + if string.find(tableWidth, "%") < 0: + for count in range(len(relParts)): + if foStylesheet: + pixels = relParts[count] + inches = pixels / pixelsPerInch + widths.append("%4.2fin" % inches) + else: + widths.append(relParts[count]) + else: + for count in range(len(relParts)): + rel = relParts[count] / absTotal * 100 + widths.append(rel) + widths = correctRoundingError(widths) + + # Danger, Will Robinson! In-place modification of the result tree! + # Side-effect free? We don' need no steenkin' side-effect free! + count = 0 + col = colChildren + while col != None: + if foStylesheet: + col.setProp("column-width", widths[count]) + else: + col.setProp("width", widths[count]) + + count = count+1 + col = col.next + + return nodeset + +def convertLength(length): + # Given "3.4in" return the width in pixels + global pixelsPerInch + global unitHash + + m = re.search('([+-]?[\d\.]+)(\S+)', length) + if m != None and m.lastindex > 1: + unit = pixelsPerInch; + if unitHash.has_key(m.group(2)): + unit = unitHash[m.group(2)]; + else: + print "Unrecognized length: " + m.group(2) + + pixels = unit * float(m.group(1)) + else: + pixels = 0 + + return pixels + +def correctRoundingError(floatWidths): + # The widths are currently floating point numbers, we have to truncate + # them back to integers and then distribute the error so that they sum + # to exactly 100%. + + totalWidth = 0 + widths = []; + for width in floatWidths: + width = math.floor(width) + widths.append(width) + totalWidth = totalWidth + math.floor(width) + + totalError = 100 - totalWidth + columnError = totalError / len(widths) + error = 0 + for count in range(len(widths)): + width = widths[count] + error = error + columnError + if error >= 1.0: + adj = math.floor(error); + error = error - adj; + widths[count] = "%d%%" % (width + adj) + else: + widths[count] = "%d%%" % width + + return widths + +def lookupVariable(tctxt, varName, default): + varString = tctxt.variableLookup(varName, None); + if varString == None: + return default + + # If it's a list, get the first element + if type(varString) == type([]): + varString = varString[0] + + # If it's not a string, it must be a node, get its content + if type(varString) != type(""): + varString = varString.content + + return varString + +# ====================================================================== +# Random notes... + +#once you have a node which is a libxml2 python xmlNode wrapper all common +#operations are possible: +# .children .last .parent .next .prev .doc for navigation +# .content .type for introspection +# .prop("attribute_name") to lookup attribute values + +# # Now make a nodeset to return +# # Danger, Will Robinson! This creates a memory leak! +# newDoc = libxml2.newDoc("1.0"); +# newColGroup = newDoc.newDocNode(None, "colgroup", None); +# newDoc.addChild(newColGroup); +# col = colgroup.children +# while col != None: +# newCol = newDoc.newDocNode(None, "col", None); +# newCol.copyPropList(col); +# newCol.setProp("width", "4") +# newColGroup.addChild(newCol) +# col = col.next diff --git a/xsl-java/xsltproc/python/xslt.py b/xsl-java/xsltproc/python/xslt.py new file mode 100644 index 000000000..b593fa8d4 --- /dev/null +++ b/xsl-java/xsltproc/python/xslt.py @@ -0,0 +1,77 @@ +#!/usr/bin/python -u + +# THIS IS ALPHA CODE AND MAY NOT WORK CORRECTLY! + +import sys +import string +import libxml2 +import libxslt +from docbook import adjustColumnWidths + +# Check the arguments + +usage = "Usage: %s xmlfile.xml xslfile.xsl [outputfile] [param1=val [param2=val]...]" % sys.argv[0] + +xmlfile = None +xslfile = None +outfile = "-" +params = {} + +try: + xmlfile = sys.argv[1] + xslfile = sys.argv[2] +except IndexError: + print usage; + sys.exit(1) + +try: + outfile = sys.argv[3] + if string.find(outfile, "=") > 0: + name, value = string.split(outfile, "=", 2); + params[name] = value + + count = 4; + while (sys.argv[count]): + try: + name, value = string.split(sys.argv[count], "=", 2); + if params.has_key(name): + print "Warning: '%s' re-specified; replacing value" % name + params[name] = value + except ValueError: + print "Invalid parameter specification: '" + sys.argv[count] + "'" + print usage + sys.exit(1); + count = count+1; +except IndexError: + pass + +# ====================================================================== +# Memory debug specific +# libxml2.debugMemory(1) + +# Setup environment +libxml2.lineNumbersDefault(1) +libxml2.substituteEntitiesDefault(1) +libxslt.registerExtModuleFunction("adjustColumnWidths", + "http://nwalsh.com/xslt/ext/xsltproc/python/Table", + adjustColumnWidths) + +# Initialize and run +styledoc = libxml2.parseFile(xslfile) +style = libxslt.parseStylesheetDoc(styledoc) +doc = libxml2.parseFile(xmlfile) +result = style.applyStylesheet(doc, params) + +# Save the result +style.saveResultToFilename(outfile, result, 0) + +# Free things up +style.freeStylesheet() +doc.freeDoc() +result.freeDoc() + +# Memory debug specific +#libxslt.cleanup() +#if libxml2.debugMemory(1) != 0: +# print "Memory leak %d bytes" % (libxml2.debugMemory(1)) +# libxml2.dumpMemory()