Table.class \
Text.class \
TextFactory.class \
+UnwrapLinks.class \
+UnwrapLinksEmitter.class \
Verbatim.class
--- /dev/null
+// 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;
+
+/**
+ * <p>Saxon extension for unwrapping nested links</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2002 Norman Walsh.</p>
+ *
+ * <p>This class provides a
+ * <a href="http://saxon.sf.net/">Saxon 6.*</a>
+ * implementation of a link unwrapper.</p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @version $Id$
+ *
+ */
+public class UnwrapLinks {
+ /** True if the stylesheet is producing formatting objects */
+ private static boolean foStylesheet = false;
+
+ /**
+ * <p>Constructor for UnwrapLinks</p>
+ *
+ * <p>All of the methods are static, so the constructor does nothing.</p>
+ */
+ public UnwrapLinks() {
+ }
+
+ /**
+ * <p>Find the string value of a stylesheet variable or parameter</p>
+ *
+ * <p>Returns the string value of <code>varName</code> in the current
+ * <code>context</code>. Returns the empty string if the variable is
+ * not defined.</p>
+ *
+ * @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 "";
+ }
+ }
+
+ /**
+ * <p>Setup the parameters associated with unwrapping links</p>
+ *
+ * @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"));
+ }
+
+ /**
+ * <p>Unwrap links</p>
+ *
+ * @param rtf 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;
+ }
+ }
+}
--- /dev/null
+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;
+
+/**
+ * <p>Saxon extension to unwrap links in a result tree fragment.</p>
+ *
+ * <p>$Id$</p>
+ *
+ * <p>Copyright (C) 2000, 2002 Norman Walsh.</p>
+ *
+ * <p>This class provides the guts of a
+ * <a href="http://saxon.sf.net/">Saxon 6.*</a>
+ * implementation of a link unwrapper.</p>
+ *
+ * <p>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 <i>single</i> level of unwrapping
+ * is performed. This is clearly a crude implementation.
+ * </p>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>1.0</dt>
+ * <dd><p>Initial release.</p></dd>
+ * </dl>
+ *
+ * @author Norman Walsh
+ * <a href="mailto:ndw@nwalsh.com">ndw@nwalsh.com</a>
+ *
+ * @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;
+
+
+ /** <p>Constructor for the UnwrapLinksEmitter.</p>
+ *
+ * @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;
+ }
+
+ /**
+ * <p>A private class for maintaining the information required to call
+ * the startElement method.</p>
+ *
+ * <p>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.</p>
+ */
+ 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;
+ }
+ }
+}
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xlink='http://www.w3.org/1999/xlink'
- exclude-result-prefixes="xlink"
+ xmlns:suwl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.UnwrapLinks"
+ exclude-result-prefixes="xlink suwl"
version='1.0'>
<!-- ********************************************************************
<xsl:apply-templates/>
</xsl:param>
- <xsl:choose>
- <xsl:when test="$node/@xlink:href
- and (not($node/@xlink:type) or $node/@xlink:type='simple')">
- <a>
- <xsl:if test="@xlink.title">
- <xsl:attribute name="title">
- <xsl:value-of select="@xlink:title"/>
- </xsl:attribute>
- </xsl:if>
+ <xsl:variable name="link">
+ <xsl:choose>
+ <xsl:when test="$node/@xlink:href
+ and (not($node/@xlink:type) or $node/@xlink:type='simple')">
+ <a>
+ <xsl:if test="@xlink.title">
+ <xsl:attribute name="title">
+ <xsl:value-of select="@xlink:title"/>
+ </xsl:attribute>
+ </xsl:if>
- <xsl:attribute name="href">
- <xsl:choose>
- <!-- if the href starts with # and does not contain an "(" -->
- <!-- or if the href starts with #xpointer(id(, it's just an ID -->
- <xsl:when test="starts-with(@xlink:href,'#')
- and (not(contains(@xlink:href,'('))
- or starts-with(@xlink:href,'#xpointer(id('))">
- <xsl:variable name="idref">
- <xsl:call-template name="xpointer.idref">
- <xsl:with-param name="xpointer" select="@xlink:href"/>
- </xsl:call-template>
- </xsl:variable>
+ <xsl:attribute name="href">
+ <xsl:choose>
+ <!-- if the href starts with # and does not contain an "(" -->
+ <!-- or if the href starts with #xpointer(id(, it's just an ID -->
+ <xsl:when test="starts-with(@xlink:href,'#')
+ and (not(contains(@xlink:href,'('))
+ or starts-with(@xlink:href,'#xpointer(id('))">
+ <xsl:variable name="idref">
+ <xsl:call-template name="xpointer.idref">
+ <xsl:with-param name="xpointer" select="@xlink:href"/>
+ </xsl:call-template>
+ </xsl:variable>
- <xsl:variable name="targets" select="key('id',$idref)"/>
- <xsl:variable name="target" select="$targets[1]"/>
+ <xsl:variable name="targets" select="key('id',$idref)"/>
+ <xsl:variable name="target" select="$targets[1]"/>
- <xsl:call-template name="check.id.unique">
- <xsl:with-param name="linkend" select="@linkend"/>
- </xsl:call-template>
+ <xsl:call-template name="check.id.unique">
+ <xsl:with-param name="linkend" select="@linkend"/>
+ </xsl:call-template>
- <xsl:choose>
- <xsl:when test="count($target) = 0">
- <xsl:message>
- <xsl:text>XLink to nonexistent id: </xsl:text>
- <xsl:value-of select="$idref"/>
- </xsl:message>
- <xsl:text>???</xsl:text>
- </xsl:when>
- <xsl:otherwise>
- <xsl:call-template name="href.target">
- <xsl:with-param name="object" select="$target"/>
- </xsl:call-template>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:when>
-
- <!-- otherwise it's a URI -->
- <xsl:otherwise>
- <xsl:value-of select="@xlink:href"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:attribute>
+ <xsl:choose>
+ <xsl:when test="count($target) = 0">
+ <xsl:message>
+ <xsl:text>XLink to nonexistent id: </xsl:text>
+ <xsl:value-of select="$idref"/>
+ </xsl:message>
+ <xsl:text>???</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:call-template name="href.target">
+ <xsl:with-param name="object" select="$target"/>
+ </xsl:call-template>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+
+ <!-- otherwise it's a URI -->
+ <xsl:otherwise>
+ <xsl:value-of select="@xlink:href"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:attribute>
+ <xsl:copy-of select="$content"/>
+ </a>
+ </xsl:when>
+ <xsl:otherwise>
<xsl:copy-of select="$content"/>
- </a>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="function-available('suwl:unwrapLinks')">
+ <xsl:copy-of select="suwl:unwrapLinks($link)"/>
</xsl:when>
<xsl:otherwise>
- <xsl:copy-of select="$content"/>
+ <xsl:copy-of select="$link"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
- xmlns:doc="http://nwalsh.com/xsl/documentation/1.0"
- exclude-result-prefixes="doc"
+ xmlns:suwl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.UnwrapLinks"
+ exclude-result-prefixes="suwl"
version='1.0'>
<!-- ********************************************************************
</xsl:template>
<xsl:template match="ulink" name="ulink">
- <a>
- <xsl:if test="@id">
- <xsl:attribute name="name">
- <xsl:value-of select="@id"/>
- </xsl:attribute>
- </xsl:if>
- <xsl:attribute name="href"><xsl:value-of select="@url"/></xsl:attribute>
- <xsl:if test="$ulink.target != ''">
- <xsl:attribute name="target">
- <xsl:value-of select="$ulink.target"/>
- </xsl:attribute>
- </xsl:if>
- <xsl:choose>
- <xsl:when test="count(child::node())=0">
- <xsl:value-of select="@url"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:apply-templates/>
- </xsl:otherwise>
- </xsl:choose>
- </a>
+ <xsl:variable name="link">
+ <a>
+ <xsl:if test="@id">
+ <xsl:attribute name="name">
+ <xsl:value-of select="@id"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:attribute name="href"><xsl:value-of select="@url"/></xsl:attribute>
+ <xsl:if test="$ulink.target != ''">
+ <xsl:attribute name="target">
+ <xsl:value-of select="$ulink.target"/>
+ </xsl:attribute>
+ </xsl:if>
+ <xsl:choose>
+ <xsl:when test="count(child::node())=0">
+ <xsl:value-of select="@url"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:apply-templates/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </a>
+ </xsl:variable>
+
+ <xsl:choose>
+ <xsl:when test="function-available('suwl:unwrapLinks')">
+ <xsl:copy-of select="suwl:unwrapLinks($link)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:copy-of select="$link"/>
+ </xsl:otherwise>
+ </xsl:choose>
</xsl:template>
<xsl:template match="olink" name="olink">