diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libjava/classpath/gnu/xml/transform/Stylesheet.java | |
download | cbb-gcc-4.6.4-upstream.tar.bz2 cbb-gcc-4.6.4-upstream.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libjava/classpath/gnu/xml/transform/Stylesheet.java')
-rw-r--r-- | libjava/classpath/gnu/xml/transform/Stylesheet.java | 1772 |
1 files changed, 1772 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/xml/transform/Stylesheet.java b/libjava/classpath/gnu/xml/transform/Stylesheet.java new file mode 100644 index 000000000..786b258f5 --- /dev/null +++ b/libjava/classpath/gnu/xml/transform/Stylesheet.java @@ -0,0 +1,1772 @@ +/* Stylesheet.java -- + Copyright (C) 2004,2006 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package gnu.xml.transform; + +import gnu.java.lang.CPStringBuilder; + +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import javax.xml.XMLConstants; +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.xpath.XPathFunction; +import javax.xml.xpath.XPathFunctionResolver; +import javax.xml.xpath.XPathExpressionException; +import org.w3c.dom.Attr; +import org.w3c.dom.Document; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.Text; +import org.w3c.dom.UserDataHandler; +import gnu.xml.xpath.Expr; +import gnu.xml.xpath.NameTest; +import gnu.xml.xpath.NodeTypeTest; +import gnu.xml.xpath.Pattern; +import gnu.xml.xpath.Selector; +import gnu.xml.xpath.Root; +import gnu.xml.xpath.Test; +import gnu.xml.xpath.XPathImpl; + +/** + * An XSL stylesheet. + * + * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> + */ +class Stylesheet + implements NamespaceContext, XPathFunctionResolver, UserDataHandler, Cloneable +{ + + static final String XSL_NS = "http://www.w3.org/1999/XSL/Transform"; + private static final NameTest STYLESHEET_PRESERVE_TEXT = + new NameTest(new QName(XSL_NS, "text"), false, false); + + static final int OUTPUT_XML = 0; + static final int OUTPUT_HTML = 1; + static final int OUTPUT_TEXT = 2; + + final TransformerFactoryImpl factory; + TransformerImpl transformer; + Stylesheet parent; + final XPathImpl xpath; + final String systemId; + final int precedence; + + final boolean debug; + + /** + * Version of XSLT. + */ + String version; + + Collection<String> extensionElementPrefixes; + Collection<String> excludeResultPrefixes; + + /** + * Set of element names for which we should strip whitespace. + */ + Set<StrippingInstruction> stripSpace; + + /** + * Set of element names for which we should preserve whitespace. + */ + Set<StrippingInstruction> preserveSpace; + + /** + * Output options. + */ + Node output; + int outputMethod; + String outputVersion; + String outputEncoding; + boolean outputOmitXmlDeclaration; + boolean outputStandalone; + String outputPublicId; + String outputSystemId; + Collection<String> outputCdataSectionElements; + boolean outputIndent; + String outputMediaType; + + /** + * Keys. + */ + Collection<Key> keys; + + /** + * Decimal formats. + */ + Map<String,DecimalFormat> decimalFormats; + + /** + * Namespace aliases. + */ + Map<String,String> namespaceAliases; + + /** + * Attribute-sets. + */ + List<AttributeSet> attributeSets; + + /** + * Variables. + */ + List<ParameterNode> variables; + + /** + * Variable and parameter bindings. + */ + Bindings bindings; + + /** + * Templates. + */ + LinkedList<Template> templates; + + TemplateNode builtInNodeTemplate; + TemplateNode builtInTextTemplate; + + /** + * Holds the current node while parsing. + * Necessary to associate the document function with its declaring node, + * to resolve namespaces, and to maintain the current node for the + * current() function. + */ + Node current; + + /** + * Set by a terminating message. + */ + transient boolean terminated; + + /** + * Current template in force. + */ + transient Template currentTemplate; + + Stylesheet(TransformerFactoryImpl factory, + Stylesheet parent, + Document doc, + String systemId, + int precedence) + throws TransformerConfigurationException + { + this.factory = factory; + this.systemId = systemId; + this.precedence = precedence; + this.parent = parent; + extensionElementPrefixes = new HashSet<String>(); + excludeResultPrefixes = new HashSet<String>(); + stripSpace = new LinkedHashSet<StrippingInstruction>(); + preserveSpace = new LinkedHashSet<StrippingInstruction>(); + outputCdataSectionElements = new LinkedHashSet<String>(); + xpath = (XPathImpl) factory.xpathFactory.newXPath(); + xpath.setNamespaceContext(this); + if (parent == null) + { + bindings = new Bindings(this); + attributeSets = new LinkedList<AttributeSet>(); + variables = new LinkedList<ParameterNode>(); + namespaceAliases = new LinkedHashMap<String,String>(); + templates = new LinkedList<Template>(); + keys = new LinkedList<Key>(); + decimalFormats = new LinkedHashMap<String,DecimalFormat>(); + initDefaultDecimalFormat(); + xpath.setXPathFunctionResolver(this); + } + else + { + /* Test for import circularity */ + for (Stylesheet ctx = this; ctx.parent != null; ctx = ctx.parent) + { + if (systemId != null && systemId.equals(ctx.parent.systemId)) + { + String msg = "circularity importing " + systemId; + throw new TransformerConfigurationException(msg); + } + } + /* OK */ + Stylesheet root = getRootStylesheet(); + bindings = root.bindings; + attributeSets = root.attributeSets; + variables = root.variables; + namespaceAliases = root.namespaceAliases; + templates = root.templates; + keys = root.keys; + decimalFormats = root.decimalFormats; + xpath.setXPathFunctionResolver(root); + } + xpath.setXPathVariableResolver(bindings); + + Test anyNode = new NodeTypeTest((short) 0); + List<Test> tests = Collections.singletonList(anyNode); + builtInNodeTemplate = + new ApplyTemplatesNode(new Selector(Selector.CHILD, tests), + null, null, null, true); + builtInTextTemplate = + new ValueOfNode(new Selector(Selector.SELF, tests), + false); + + parse(doc.getDocumentElement(), true); + current = doc; // Alow namespace resolution during processing + + debug = ("yes".equals(System.getProperty("xsl.debug"))); + + if (debug) + { + System.err.println("Stylesheet: " + doc.getDocumentURI()); + for (Template t : templates) + { + t.list(System.err); + System.err.println("--------------------"); + } + } + } + + Stylesheet getRootStylesheet() + { + Stylesheet stylesheet = this; + while (stylesheet.parent != null) + stylesheet = stylesheet.parent; + return stylesheet; + } + + void initDefaultDecimalFormat() + { + DecimalFormat defaultDecimalFormat = new DecimalFormat(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator('.'); + symbols.setGroupingSeparator(','); + symbols.setPercent('%'); + symbols.setPerMill('\u2030'); + symbols.setZeroDigit('0'); + symbols.setDigit('#'); + symbols.setPatternSeparator(';'); + symbols.setInfinity("Infinity"); + symbols.setNaN("NaN"); + symbols.setMinusSign('-'); + defaultDecimalFormat.setDecimalFormatSymbols(symbols); + decimalFormats.put(null, defaultDecimalFormat); + } + + // -- Cloneable -- + + public Object clone() + { + try + { + Stylesheet clone = (Stylesheet) super.clone(); + clone.bindings = (Bindings) bindings.clone(); + + LinkedList<Template> templates2 = new LinkedList<Template>(); + for (Template t : templates) + { + templates2.add(t.clone(clone)); + } + clone.templates = templates2; + + LinkedList<AttributeSet> attributeSets2 = new LinkedList<AttributeSet>(); + for (AttributeSet as : attributeSets) + { + attributeSets2.add(as.clone(clone)); + } + clone.attributeSets = attributeSets2; + + LinkedList<ParameterNode> variables2 = new LinkedList<ParameterNode>(); + for (ParameterNode var : variables) + { + variables2.add(var.clone(clone)); + } + clone.variables = variables2; + + LinkedList<Key> keys2 = new LinkedList<Key>(); + for (Key k : keys) + { + keys2.add(k.clone(clone)); + } + clone.keys = keys2; + + return clone; + } + catch (CloneNotSupportedException e) + { + throw new Error(e.getMessage()); + } + } + + // -- Variable evaluation -- + + void initTopLevelVariables(Node context) + throws TransformerException + { + current = context; + // Sort the variables into order + // See XSLT 11.4: "If the template or expression specifying the value of + // a global variable x references a global variable y, then the value + // for y must be computed before the value of x." + List<ParameterNode> topLevel = new ArrayList<ParameterNode>(variables); + Collections.sort(topLevel); + for (ParameterNode var : topLevel) + { + bindings.set(var.name, + var.getValue(this, null, context, 1, 1), + var.type); + } + current = null; + } + + // -- NamespaceContext -- + + public String getNamespaceURI(String prefix) + { + return (current == null) ? null : current.lookupNamespaceURI(prefix); + } + + public String getPrefix(String namespaceURI) + { + return (current == null) ? null : current.lookupPrefix(namespaceURI); + } + + public Iterator<String> getPrefixes(String namespaceURI) + { + // TODO + return Collections.singleton(getPrefix(namespaceURI)).iterator(); + } + + final QName getQName(String name) + { + String localName = name, uri = null, prefix = null; + int ci = name.indexOf(':'); + if (ci != -1) + { + prefix = name.substring(0, ci); + localName = name.substring(ci + 1); + uri = getNamespaceURI(prefix); + } + return new QName(uri, localName, prefix); + } + + // -- Template selection -- + + TemplateNode getTemplate(QName mode, Node context, boolean applyImports) + throws TransformerException + { + if (debug) + System.err.println("getTemplate: mode="+mode+" context="+context); + Template selected = null; + for (Template t : templates) + { + boolean isMatch = t.matches(mode, context); + if (applyImports) + { + if (currentTemplate == null) + { + String msg = "current template may not be null " + + "during apply-imports"; + throw new TransformerException(msg); + } + if (!currentTemplate.imports(t)) + isMatch = false; + } + //System.err.println("\t"+context+" "+t+"="+isMatch); + if (isMatch) + { + // Conflict resolution + // @see http://www.w3.org/TR/xslt#conflict + if (selected == null) + selected = t; + else + { + if (t.precedence < selected.precedence || + t.priority < selected.priority) + continue; + selected = t; + } + } + } + if (selected == null) + { + // Apply built-in template + // Current template is unchanged + if (debug) + System.err.println("\tbuiltInTemplate context="+context); + switch (context.getNodeType()) + { + case Node.ELEMENT_NODE: + case Node.DOCUMENT_NODE: + case Node.DOCUMENT_FRAGMENT_NODE: + case Node.PROCESSING_INSTRUCTION_NODE: + case Node.COMMENT_NODE: + return builtInNodeTemplate; + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + case Node.ATTRIBUTE_NODE: + return builtInTextTemplate; + default: + return null; + } + } + // Set current template + currentTemplate = selected; + if (debug) + System.err.println("\ttemplate="+currentTemplate+" context="+context); + return currentTemplate.node; + } + + TemplateNode getTemplate(QName mode, QName name) + throws TransformerException + { + Template selected = null; + for (Template t : templates) + { + boolean isMatch = t.matches(name); + if (isMatch) + { + // Conflict resolution + // @see http://www.w3.org/TR/xslt#conflict + if (selected == null) + selected = t; + else + { + if (t.precedence < selected.precedence || + t.priority < selected.priority) + continue; + selected = t; + } + } + } + if (selected == null) + return null; + return selected.node; + } + + /** + * template + */ + final Template parseTemplate(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + String n = getAttribute(attrs, "name"); + QName name = (n == null) ? null : getQName(n); + String m = getAttribute(attrs, "match"); + Pattern match = null; + if (m != null) + { + try + { + match = (Pattern) xpath.compile(m); + } + catch (ClassCastException e) + { + String msg = "illegal pattern: " + m; + throw new TransformerConfigurationException(msg); + } + } + String p = getAttribute(attrs, "priority"); + String mm = getAttribute(attrs, "mode"); + QName mode = (mm == null) ? null : getQName(mm); + Node children = node.getFirstChild(); + return new Template(this, name, match, parse(children), + precedence, p, mode); + } + + /** + * output + */ + final void parseOutput(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + output = node; + String method = getAttribute(attrs, "method"); + if ("xml".equals(method) || method == null) + outputMethod = OUTPUT_XML; + else if ("html".equals(method)) + outputMethod = OUTPUT_HTML; + else if ("text".equals(method)) + outputMethod = OUTPUT_TEXT; + else + { + String msg = "unsupported output method: " + method; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + outputPublicId = getAttribute(attrs, "doctype-public"); + outputSystemId = getAttribute(attrs, "doctype-system"); + outputEncoding = getAttribute(attrs, "encoding"); + String indent = getAttribute(attrs, "indent"); + if (indent != null) + outputIndent = "yes".equals(indent); + outputVersion = getAttribute(attrs, "version"); + String omitXmlDecl = getAttribute(attrs, "omit-xml-declaration"); + if (omitXmlDecl != null) + outputOmitXmlDeclaration = "yes".equals(omitXmlDecl); + String standalone = getAttribute(attrs, "standalone"); + if (standalone != null) + outputStandalone = "yes".equals(standalone); + outputMediaType = getAttribute(attrs, "media-type"); + String cdataSectionElements = + getAttribute(attrs, "cdata-section-elements"); + if (cdataSectionElements != null) + { + StringTokenizer st = new StringTokenizer(cdataSectionElements, " "); + while (st.hasMoreTokens()) + outputCdataSectionElements.add(st.nextToken()); + } + } + + /** + * key + */ + final void parseKey(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + String n = getRequiredAttribute(attrs, "name", node); + String m = getRequiredAttribute(attrs, "match", node); + String u = getRequiredAttribute(attrs, "use", node); + QName name = getQName(n); + Expr use = (Expr) xpath.compile(u); + try + { + Pattern match = (Pattern) xpath.compile(m); + Key key = new Key(name, match, use); + keys.add(key); + } + catch (ClassCastException e) + { + throw new TransformerConfigurationException("invalid pattern: " + m); + } + } + + /** + * decimal-format + */ + final void parseDecimalFormat(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + String dfName = getAttribute(attrs, "name"); + DecimalFormat df = new DecimalFormat(); + DecimalFormatSymbols symbols = new DecimalFormatSymbols(); + symbols.setDecimalSeparator(parseDFChar(attrs, "decimal-separator", '.')); + symbols.setGroupingSeparator(parseDFChar(attrs, "grouping-separator", ',')); + symbols.setInfinity(parseDFString(attrs, "infinity", "Infinity")); + symbols.setMinusSign(parseDFChar(attrs, "minus-sign", '-')); + symbols.setNaN(parseDFString(attrs, "NaN", "NaN")); + symbols.setPercent(parseDFChar(attrs, "percent", '%')); + symbols.setPerMill(parseDFChar(attrs, "per-mille", '\u2030')); + symbols.setZeroDigit(parseDFChar(attrs, "zero-digit", '0')); + symbols.setDigit(parseDFChar(attrs, "digit", '#')); + symbols.setPatternSeparator(parseDFChar(attrs, "pattern-separator", ';')); + df.setDecimalFormatSymbols(symbols); + decimalFormats.put(dfName, df); + } + + private final char parseDFChar(NamedNodeMap attrs, String name, char def) + throws TransformerConfigurationException + { + Node attr = attrs.getNamedItem(name); + try + { + return (attr == null) ? def : attr.getNodeValue().charAt(0); + } + catch (StringIndexOutOfBoundsException e) + { + throw new TransformerConfigurationException("empty attribute '" + + name + + "' in decimal-format", e); + } + } + + private final String parseDFString(NamedNodeMap attrs, String name, + String def) + { + Node attr = attrs.getNamedItem(name); + return (attr == null) ? def : attr.getNodeValue(); + } + + /** + * namespace-alias + */ + final void parseNamespaceAlias(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException + { + String sp = getRequiredAttribute(attrs, "stylesheet-prefix", node); + String rp = getRequiredAttribute(attrs, "result-prefix", node); + namespaceAliases.put(sp, rp); + } + + /** + * attribute-set + */ + final void parseAttributeSet(Node node, NamedNodeMap attrs) + throws TransformerConfigurationException, XPathExpressionException + { + TemplateNode children = parse(node.getFirstChild()); + String name = getRequiredAttribute(attrs, "name", node); + String uas = getAttribute(attrs, "use-attribute-sets"); + attributeSets.add(new AttributeSet(children, name, uas)); + } + + /** + * Parse top-level elements. + */ + void parse(Node node, boolean root) + throws TransformerConfigurationException + { + while (node != null) + { + current = node; + doParse(node, root); + node = node.getNextSibling(); + } + } + + void doParse(Node node, boolean root) + throws TransformerConfigurationException + { + try + { + String namespaceUri = node.getNamespaceURI(); + if (XSL_NS.equals(namespaceUri) && + node.getNodeType() == Node.ELEMENT_NODE) + { + String name = node.getLocalName(); + NamedNodeMap attrs = node.getAttributes(); + if ("stylesheet".equals(name)) + { + version = getAttribute(attrs, "version"); + String eep = getAttribute(attrs, "extension-element-prefixes"); + if (eep != null) + { + StringTokenizer st = new StringTokenizer(eep); + while (st.hasMoreTokens()) + { + extensionElementPrefixes.add(st.nextToken()); + } + } + String erp = getAttribute(attrs, "exclude-result-prefixes"); + if (erp != null) + { + StringTokenizer st = new StringTokenizer(erp); + while (st.hasMoreTokens()) + { + excludeResultPrefixes.add(st.nextToken()); + } + } + parse(node.getFirstChild(), false); + } + else if ("template".equals(name)) + templates.add(parseTemplate(node, attrs)); + else if ("param".equals(name) || + "variable".equals(name)) + { + int type = "variable".equals(name) ? + Bindings.VARIABLE : Bindings.PARAM; + TemplateNode content = parse(node.getFirstChild()); + QName paramName = + getQName(getRequiredAttribute(attrs, "name", node)); + String select = getAttribute(attrs, "select"); + ParameterNode param; + if (select != null && select.length() > 0) + { + if (content != null) + { + String msg = "parameter '" + paramName + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + param = new ParameterNode(paramName, expr, type); + } + else + { + param = new ParameterNode(paramName, null, type); + param.children = content; + } + variables.add(param); + } + else if ("include".equals(name) || "import".equals(name)) + { + int delta = "import".equals(name) ? -1 : 0; + String href = getRequiredAttribute(attrs, "href", node); + Source source; + synchronized (factory.resolver) + { + if (transformer != null) + { + factory.resolver + .setUserResolver(transformer.getURIResolver()); + factory.resolver + .setUserListener(transformer.getErrorListener()); + } + source = factory.resolver.resolve(systemId, href); + } + factory.newStylesheet(source, precedence + delta, this); + } + else if ("output".equals(name)) + parseOutput(node, attrs); + else if ("preserve-space".equals(name)) + { + String elements = + getRequiredAttribute(attrs, "elements", node); + StringTokenizer st = new StringTokenizer(elements, + " \t\n\r"); + while (st.hasMoreTokens()) + { + NameTest element = parseNameTest(st.nextToken()); + preserveSpace.add(new StrippingInstruction(element, + precedence)); + } + } + else if ("strip-space".equals(name)) + { + String elements = + getRequiredAttribute(attrs, "elements", node); + StringTokenizer st = new StringTokenizer(elements, + " \t\n\r"); + while (st.hasMoreTokens()) + { + NameTest element = parseNameTest(st.nextToken()); + stripSpace.add(new StrippingInstruction(element, + precedence)); + } + } + else if ("key".equals(name)) + parseKey(node, attrs); + else if ("decimal-format".equals(name)) + parseDecimalFormat(node, attrs); + else if ("namespace-alias".equals(name)) + parseNamespaceAlias(node, attrs); + else if ("attribute-set".equals(name)) + parseAttributeSet(node, attrs); + } + else if (root) + { + // Literal document element + Attr versionNode = + ((Element)node).getAttributeNodeNS(XSL_NS, "version"); + if (versionNode == null) + { + String msg = "no xsl:version attribute on literal result node"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + version = versionNode.getValue(); + Node rootClone = node.cloneNode(true); + NamedNodeMap attrs = rootClone.getAttributes(); + attrs.removeNamedItemNS(XSL_NS, "version"); + templates.add(new Template(this, null, new Root(), + parse(rootClone), + precedence, + null, + null)); + } + else + { + // Skip unknown elements, text, comments, etc + } + } + catch (TransformerException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + catch (DOMException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + catch (XPathExpressionException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + } + + final NameTest parseNameTest(String token) + { + if ("*".equals(token)) + return new NameTest(null, true, true); + else if (token.endsWith(":*")) + { + QName qName = getQName(token); + return new NameTest(qName, true, false); + } + else + { + QName qName = getQName(token); + return new NameTest(qName, false, false); + } + } + + final TemplateNode parseAttributeValueTemplate(String value, Node source) + throws TransformerConfigurationException, XPathExpressionException + { + current = source; + // Tokenize + int len = value.length(); + int off = 0; + List<String> tokens = new ArrayList<String>(); // text tokens + List<Boolean> types = new ArrayList<Boolean>(); // literal or expression + int depth = 0; + for (int i = 0; i < len; i++) + { + char c = value.charAt(i); + if (c == '{') + { + if (i < (len - 1) && value.charAt(i + 1) == '{') + { + tokens.add(value.substring(off, i + 1)); + types.add(Boolean.FALSE); + i++; + off = i + 1; + continue; + } + if (depth == 0) + { + if (i - off > 0) + { + tokens.add(value.substring(off, i)); + types.add(Boolean.FALSE); + } + off = i + 1; + } + depth++; + } + else if (c == '}') + { + if (i < (len - 1) && value.charAt(i + 1) == '}') + { + tokens.add(value.substring(off, i + 1)); + types.add(Boolean.FALSE); + i++; + off = i + 1; + continue; + } + if (depth == 1) + { + if (i - off > 0) + { + tokens.add(value.substring(off, i)); + types.add(Boolean.TRUE); + } + else + { + String msg = "attribute value template " + + "must contain expression: " + value; + DOMSourceLocator l = new DOMSourceLocator(source); + throw new TransformerConfigurationException(msg, l); + } + off = i + 1; + } + depth--; + } + } + if (depth > 0) + { + String msg = "invalid attribute value template: " + value; + throw new TransformerConfigurationException(msg); + } + if (len - off > 0) + { + // Trailing text + tokens.add(value.substring(off)); + types.add(Boolean.FALSE); + } + + // Construct template node tree + TemplateNode ret = null; + Document doc = source.getOwnerDocument(); + len = tokens.size(); + for (int i = len - 1; i >= 0; i--) + { + String token = tokens.get(i); + Boolean type = types.get(i); + if (type == Boolean.TRUE) + { + // Expression text + Expr select = (Expr) xpath.compile(token); + TemplateNode ret2 = new ValueOfNode(select, false); + ret2.next = ret; + ret = ret2; + } + else + { + // Verbatim text + TemplateNode ret2 = new LiteralNode(doc.createTextNode(token)); + ret2.next = ret; + ret = ret2; + } + } + return ret; + } + + /** + * Whitespace stripping. + * @param text the text node + * @param source true if a source node, false if a stylesheet text node + * @see http://www.w3.org/TR/xslt#strip + */ + boolean isPreserved(Text text, boolean source) + throws TransformerConfigurationException + { + // Check characters in text + String value = text.getData(); + if (value != null) + { + int len = value.length(); + for (int i = 0; i < len; i++) + { + char c = value.charAt(i); + if (c != 0x20 && c != 0x09 && c != 0x0a && c != 0x0d) + return true; + } + } + // Check parent node + Node ctx = text.getParentNode(); + if (source) + { + // Source document text node + boolean preserve = true; + float psPriority = 0.0f, ssPriority = 0.0f; + if (!stripSpace.isEmpty()) + { + // Conflict resolution + StrippingInstruction ssi = null, psi = null; + for (StrippingInstruction si : stripSpace) + { + if (si.element.matches(ctx, 1, 1)) + { + if (ssi != null) + { + if (si.precedence < ssi.precedence) + continue; + float p = si.getPriority(); + if (p < ssPriority) + continue; + } + ssi = si; + } + } + for (StrippingInstruction si : preserveSpace) + { + if (si.element.matches(ctx, 1, 1)) + { + if (psi != null) + { + if (si.precedence < psi.precedence) + continue; + float p = si.getPriority(); + if (p < psPriority) + continue; + } + psi = si; + } + } + if (ssi != null) + { + if (psi != null) + { + if (psi.precedence < ssi.precedence) + preserve = false; + else if (psPriority < ssPriority) + preserve = false; + } + else + preserve = false; + } + } + if (preserve) + return true; + } + else + { + // Stylesheet text node + if (STYLESHEET_PRESERVE_TEXT.matches(ctx, 1, 1)) + return true; + } + // Check whether any ancestor specified xml:space + while (ctx != null) + { + if (ctx.getNodeType() == Node.ELEMENT_NODE) + { + Element element = (Element) ctx; + String xmlSpace = element.getAttribute("xml:space"); + if ("default".equals(xmlSpace)) + break; + else if ("preserve".equals(xmlSpace)) + return true; + else if (xmlSpace.length() > 0) + { + String msg = "Illegal value for xml:space: " + xmlSpace; + throw new TransformerConfigurationException(msg); + } + } + ctx = ctx.getParentNode(); + } + return false; + } + + public XPathFunction resolveFunction(QName name, int arity) + { + String uri = name.getNamespaceURI(); + if (XSL_NS.equals(uri) || uri == null || uri.length() == 0) + { + String localName = name.getLocalPart(); + if ("document".equals(localName) && (arity == 1 || arity == 2)) + { + if (current == null) + throw new RuntimeException("current is null"); + return new DocumentFunction(getRootStylesheet(), current); + } + else if ("key".equals(localName) && (arity == 2)) + return new KeyFunction(getRootStylesheet()); + else if ("format-number".equals(localName) && + (arity == 2 || arity == 3)) + return new FormatNumberFunction(getRootStylesheet()); + else if ("current".equals(localName) && (arity == 0)) + return new CurrentFunction(getRootStylesheet()); + else if ("unparsed-entity-uri".equals(localName) && (arity == 1)) + return new UnparsedEntityUriFunction(); + else if ("generate-id".equals(localName) && + (arity == 1 || arity == 0)) + return new GenerateIdFunction(); + else if ("system-property".equals(localName) && (arity == 1)) + return new SystemPropertyFunction(); + else if ("element-available".equals(localName) && (arity == 1)) + return new ElementAvailableFunction(new NamespaceProxy(current)); + else if ("function-available".equals(localName) && (arity == 1)) + return new FunctionAvailableFunction(new NamespaceProxy(current)); + } + return null; + } + + // -- Parsing -- + + /** + * apply-templates + */ + final TemplateNode parseApplyTemplates(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String m = getAttribute(attrs, "mode"); + QName mode = (m == null) ? null : getQName(m); + String s = getAttribute(attrs, "select"); + if (s == null) + s = "child::node()"; + Node children = node.getFirstChild(); + List<SortKey> sortKeys = parseSortKeys(children); + List<WithParam> withParams = parseWithParams(children); + Expr select = (Expr) xpath.compile(s); + return new ApplyTemplatesNode(select, mode, + sortKeys, withParams, false); + } + + /** + * call-template + */ + final TemplateNode parseCallTemplate(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String n = getRequiredAttribute(attrs, "name", node); + QName name = getQName(n); + Node children = node.getFirstChild(); + List<WithParam> withParams = parseWithParams(children); + return new CallTemplateNode(name, withParams); + } + + /** + * value-of + */ + final TemplateNode parseValueOf(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + String doe = getAttribute(attrs, "disable-output-escaping"); + boolean d = "yes".equals(doe); + Expr select = (Expr) xpath.compile(s); + return new ValueOfNode(select, d); + } + + /** + * for-each + */ + final TemplateNode parseForEach(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + Node children = node.getFirstChild(); + List<SortKey> sortKeys = parseSortKeys(children); + Expr select = (Expr) xpath.compile(s); + ForEachNode ret = new ForEachNode(select, sortKeys); + ret.children = parse(children); + return ret; + } + + /** + * if + */ + final TemplateNode parseIf(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getRequiredAttribute(attrs, "test", node); + Expr test = (Expr) xpath.compile(t); + Node children = node.getFirstChild(); + IfNode ret = new IfNode(test); + ret.children = parse(children); + return ret; + } + + /** + * when + */ + final TemplateNode parseWhen(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getRequiredAttribute(attrs, "test", node); + Expr test = (Expr) xpath.compile(t); + Node children = node.getFirstChild(); + WhenNode ret = new WhenNode(test); + ret.children = parse(children); + return ret; + } + + /** + * element + */ + final TemplateNode parseElement(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + String namespace = getAttribute(attrs, "namespace"); + String uas = getAttribute(attrs, "use-attribute-sets"); + TemplateNode n = parseAttributeValueTemplate(name, node); + TemplateNode ns = (namespace == null) ? null : + parseAttributeValueTemplate(namespace, node); + Node children = node.getFirstChild(); + ElementNode ret = new ElementNode(n, ns, uas, node); + ret.children = parse(children); + return ret; + } + + /** + * attribute + */ + final TemplateNode parseAttribute(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + String namespace = getAttribute(attrs, "namespace"); + TemplateNode n = parseAttributeValueTemplate(name, node); + TemplateNode ns = (namespace == null) ? null : + parseAttributeValueTemplate(namespace, node); + Node children = node.getFirstChild(); + AttributeNode ret = new AttributeNode(n, ns, node); + ret.children = parse(children); + return ret; + } + + /** + * text + */ + final TemplateNode parseText(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String doe = getAttribute(attrs, "disable-output-escaping"); + boolean d = "yes".equals(doe); + Node children = node.getFirstChild(); + TextNode ret = new TextNode(d); + ret.children = parse(children); + return ret; + } + + /** + * copy + */ + final TemplateNode parseCopy(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String uas = getAttribute(attrs, "use-attribute-sets"); + Node children = node.getFirstChild(); + CopyNode ret = new CopyNode(uas); + ret.children = parse(children); + return ret; + } + + /** + * processing-instruction + */ + final TemplateNode parseProcessingInstruction(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String name = getRequiredAttribute(attrs, "name", node); + Node children = node.getFirstChild(); + ProcessingInstructionNode ret = new ProcessingInstructionNode(name); + ret.children = parse(children); + return ret; + } + + /** + * number + */ + final TemplateNode parseNumber(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String v = getAttribute(attrs, "value"); + String ff = getAttribute(attrs, "format"); + if (ff == null) + { + ff = "1"; + } + TemplateNode format = parseAttributeValueTemplate(ff, node); + String lang = getAttribute(attrs, "lang"); + String lv = getAttribute(attrs, "letter-value"); + int letterValue = "traditional".equals(lv) ? + AbstractNumberNode.TRADITIONAL : + AbstractNumberNode.ALPHABETIC; + String gs = getAttribute(attrs, "grouping-separator"); + String gz = getAttribute(attrs, "grouping-size"); + int gz2 = (gz != null && gz.length() > 0) ? + Integer.parseInt(gz) : 1; + Node children = node.getFirstChild(); + TemplateNode ret; + if (v != null && v.length() > 0) + { + Expr value = (Expr) xpath.compile(v); + ret = new NumberNode(value, format, lang, + letterValue, gs, gz2); + } + else + { + String l = getAttribute(attrs, "level"); + int level = + "multiple".equals(l) ? NodeNumberNode.MULTIPLE : + "any".equals(l) ? NodeNumberNode.ANY : + NodeNumberNode.SINGLE; + String c = getAttribute(attrs, "count"); + String f = getAttribute(attrs, "from"); + Pattern count = null; + Pattern from = null; + if (c != null) + { + try + { + count = (Pattern) xpath.compile(c); + } + catch (ClassCastException e) + { + String msg = "invalid pattern: " + c; + throw new TransformerConfigurationException(msg); + } + } + if (f != null) + { + try + { + from = (Pattern) xpath.compile(f); + } + catch (ClassCastException e) + { + String msg = "invalid pattern: " + f; + throw new TransformerConfigurationException(msg); + } + } + ret = new NodeNumberNode(level, count, from, + format, lang, + letterValue, gs, gz2); + } + ret.children = parse(children); + return ret; + } + + /** + * copy-of + */ + final TemplateNode parseCopyOf(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String s = getRequiredAttribute(attrs, "select", node); + Expr select = (Expr) xpath.compile(s); + Node children = node.getFirstChild(); + CopyOfNode ret = new CopyOfNode(select); + ret.children = parse(children); + return ret; + } + + /** + * message + */ + final TemplateNode parseMessage(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + NamedNodeMap attrs = node.getAttributes(); + String t = getAttribute(attrs, "terminate"); + boolean terminate = "yes".equals(t); + Node children = node.getFirstChild(); + MessageNode ret = new MessageNode(terminate); + ret.children = parse(children); + return ret; + } + + /** + * Parse template-level elements. + */ + final TemplateNode parse(Node node) + throws TransformerConfigurationException + { + TemplateNode first = null; + TemplateNode previous = null; + while (node != null) + { + Node next = node.getNextSibling(); + TemplateNode tnode = doParse(node); + if (tnode != null) + { + if (first == null) + first = tnode; + if (previous != null) + previous.next = tnode; + previous = tnode; + } + node = next; + } + return first; + } + + private final TemplateNode doParse(Node node) + throws TransformerConfigurationException + { + // Hack to associate the document function with its declaring node + current = node; + try + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType()) + { + String name = node.getLocalName(); + if ("apply-templates".equals(name)) + return parseApplyTemplates(node); + else if ("call-template".equals(name)) + return parseCallTemplate(node); + else if ("value-of".equals(name)) + return parseValueOf(node); + else if ("for-each".equals(name)) + return parseForEach(node); + else if ("if".equals(name)) + return parseIf(node); + else if ("choose".equals(name)) + { + Node children = node.getFirstChild(); + ChooseNode ret = new ChooseNode(); + ret.children = parse(children); + return ret; + } + else if ("when".equals(name)) + return parseWhen(node); + else if ("otherwise".equals(name)) + { + Node children = node.getFirstChild(); + OtherwiseNode ret = new OtherwiseNode(); + ret.children = parse(children); + return ret; + } + else if ("element".equals(name)) + return parseElement(node); + else if ("attribute".equals(name)) + return parseAttribute(node); + else if ("text".equals(name)) + return parseText(node); + else if ("copy".equals(name)) + return parseCopy(node); + else if ("processing-instruction".equals(name)) + return parseProcessingInstruction(node); + else if ("comment".equals(name)) + { + Node children = node.getFirstChild(); + CommentNode ret = new CommentNode(); + ret.children = parse(children); + return ret; + } + else if ("number".equals(name)) + return parseNumber(node); + else if ("param".equals(name) || + "variable".equals(name)) + { + int type = "variable".equals(name) ? + Bindings.VARIABLE : Bindings.PARAM; + NamedNodeMap attrs = node.getAttributes(); + Node children = node.getFirstChild(); + TemplateNode content = parse(children); + QName paramName = + getQName(getRequiredAttribute(attrs, "name", node)); + String select = getAttribute(attrs, "select"); + ParameterNode ret; + if (select != null) + { + if (content != null) + { + String msg = "parameter '" + paramName + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + ret = new ParameterNode(paramName, expr, type); + } + else + { + ret = new ParameterNode(paramName, null, type); + ret.children = content; + } + return ret; + } + else if ("copy-of".equals(name)) + return parseCopyOf(node); + else if ("message".equals(name)) + return parseMessage(node); + else if ("apply-imports".equals(name)) + { + Node children = node.getFirstChild(); + ApplyImportsNode ret = new ApplyImportsNode(); + ret.children = parse(children); + return ret; + } + else + { + // xsl:fallback + // Pass over any other XSLT nodes + return null; + } + } + String prefix = node.getPrefix(); + if (extensionElementPrefixes.contains(prefix)) + { + // Check for xsl:fallback + for (Node ctx = node.getFirstChild(); ctx != null; + ctx = ctx.getNextSibling()) + { + String ctxUri = ctx.getNamespaceURI(); + if (XSL_NS.equals(ctxUri) && + "fallback".equals(ctx.getLocalName())) + { + ctx = ctx.getFirstChild(); + return (ctx == null) ? null : parse(ctx); + } + } + // Otherwise pass over extension element + return null; + } + switch (node.getNodeType()) + { + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + // Determine whether to strip whitespace + Text text = (Text) node; + if (!isPreserved(text, false)) + { + // Strip + text.getParentNode().removeChild(text); + return null; + } + break; + case Node.COMMENT_NODE: + // Ignore comments + return null; + case Node.ELEMENT_NODE: + // Check for attribute value templates and use-attribute-sets + NamedNodeMap attrs = node.getAttributes(); + boolean convert = false; + String useAttributeSets = null; + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String value = attr.getNodeValue(); + if (Stylesheet.XSL_NS.equals(attr.getNamespaceURI()) && + "use-attribute-sets".equals(attr.getLocalName())) + { + useAttributeSets = value; + convert = true; + break; + } + int start = value.indexOf('{'); + int end = value.indexOf('}'); + if (start != -1 || end != -1) + { + convert = true; + break; + } + } + if (convert) + { + // Create an element-producing template node instead + // with appropriate attribute-producing child template nodes + Node children = node.getFirstChild(); + TemplateNode child = parse(children); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String ans = attr.getNamespaceURI(); + String aname = attr.getNodeName(); + if (Stylesheet.XSL_NS.equals(ans) && + "use-attribute-sets".equals(attr.getLocalName())) + continue; + String value = attr.getNodeValue(); + TemplateNode grandchild = + parseAttributeValueTemplate(value, node); + TemplateNode n = + parseAttributeValueTemplate(aname, node); + TemplateNode ns = (ans == null) ? null : + parseAttributeValueTemplate(ans, node); + TemplateNode newChild = new AttributeNode(n, ns, attr); + newChild.children = grandchild; + newChild.next = child; + child = newChild; + } + String ename = node.getNodeName(); + TemplateNode n = parseAttributeValueTemplate(ename, node); + //TemplateNode ns = (namespaceUri == null) ? null : + // parseAttributeValueTemplate(namespaceUri, node); + TemplateNode ns = null; + ElementNode ret = new ElementNode(n, ns, useAttributeSets, + node); + ret.children = child; + return ret; + } + // Otherwise fall through + break; + } + } + catch (XPathExpressionException e) + { + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(e.getMessage(), l, e); + } + Node children = node.getFirstChild(); + LiteralNode ret = new LiteralNode(node); + ret.children = parse(children); + return ret; + } + + final List<SortKey> parseSortKeys(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + List<SortKey> ret = new LinkedList<SortKey>(); + while (node != null) + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType() && + "sort".equals(node.getLocalName())) + { + NamedNodeMap attrs = node.getAttributes(); + String s = getAttribute(attrs, "select"); + if (s == null) + s = "."; + Expr select = (Expr) xpath.compile(s); + String l = getAttribute(attrs, "lang"); + TemplateNode lang = (l == null) ? null : + parseAttributeValueTemplate(l, node); + String dt = getAttribute(attrs, "data-type"); + TemplateNode dataType = (dt == null) ? null : + parseAttributeValueTemplate(dt, node); + String o = getAttribute(attrs, "order"); + TemplateNode order = (o == null) ? null : + parseAttributeValueTemplate(o, node); + String co = getAttribute(attrs, "case-order"); + TemplateNode caseOrder = (co == null) ? null : + parseAttributeValueTemplate(co, node); + ret.add(new SortKey(select, lang, dataType, order, caseOrder)); + } + node = node.getNextSibling(); + } + return ret; + } + + final List<WithParam> parseWithParams(Node node) + throws TransformerConfigurationException, XPathExpressionException + { + List<WithParam> ret = new LinkedList<WithParam>(); + while (node != null) + { + String namespaceUri = node.getNamespaceURI(); + if (Stylesheet.XSL_NS.equals(namespaceUri) && + Node.ELEMENT_NODE == node.getNodeType() && + "with-param".equals(node.getLocalName())) + { + NamedNodeMap attrs = node.getAttributes(); + TemplateNode content = parse(node.getFirstChild()); + QName name = + getQName(getRequiredAttribute(attrs, "name", node)); + String select = getAttribute(attrs, "select"); + if (select != null) + { + if (content != null) + { + String msg = "parameter '" + name + + "' has both select and content"; + DOMSourceLocator l = new DOMSourceLocator(node); + throw new TransformerConfigurationException(msg, l); + } + Expr expr = (Expr) xpath.compile(select); + ret.add(new WithParam(name, expr)); + } + else + ret.add(new WithParam(name, content)); + } + node = node.getNextSibling(); + } + return ret; + } + + /** + * Created element nodes have a copy of the namespace nodes in the + * stylesheet, except the XSLT namespace, extension namespaces, and + * exclude-result-prefixes. + */ + final void addNamespaceNodes(Node source, Node target, Document doc, + Collection<String> elementExcludeResultPrefixes) + { + NamedNodeMap attrs = source.getAttributes(); + if (attrs != null) + { + int len = attrs.getLength(); + for (int i = 0; i < len; i++) + { + Node attr = attrs.item(i); + String uri = attr.getNamespaceURI(); + if (uri == XMLConstants.XMLNS_ATTRIBUTE_NS_URI) + { + String prefix = attr.getLocalName(); + if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix)) + prefix = "#default"; + String ns = attr.getNodeValue(); + // Should the namespace be excluded? + if (XSL_NS.equals(ns) || + extensionElementPrefixes.contains(prefix) || + elementExcludeResultPrefixes.contains(prefix) || + excludeResultPrefixes.contains(prefix)) + continue; + // Is the namespace already defined on the target? + if (prefix == "#default") + prefix = null; + if (target.lookupNamespaceURI(prefix) != null) + continue; + attr = attr.cloneNode(true); + attr = doc.adoptNode(attr); + target.getAttributes().setNamedItemNS(attr); + } + } + } + Node parent = source.getParentNode(); + if (parent != null) + addNamespaceNodes(parent, target, doc, elementExcludeResultPrefixes); + } + + static final String getAttribute(NamedNodeMap attrs, String name) + { + Node attr = attrs.getNamedItem(name); + if (attr == null) + return null; + String ret = attr.getNodeValue(); + if (ret.length() == 0) + return null; + return ret; + } + + static final String getRequiredAttribute(NamedNodeMap attrs, String name, + Node source) + throws TransformerConfigurationException + { + String value = getAttribute(attrs, name); + if (value == null || value.length() == 0) + { + String msg = + name + " attribute is required on " + source.getNodeName(); + DOMSourceLocator l = new DOMSourceLocator(source); + throw new TransformerConfigurationException(msg, l); + } + return value; + } + + // Handle user data changes when nodes are cloned etc + + public void handle(short op, String key, Object data, Node src, Node dst) + { + dst.setUserData(key, data, this); + } + + public String toString() + { + CPStringBuilder b = new CPStringBuilder(getClass().getName()); + b.append("[templates="); + b.append(templates); + b.append("]"); + return b.toString(); + } + +} |