| /* 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 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 java.util.TreeSet; |
| 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 extensionElementPrefixes; |
| Collection excludeResultPrefixes; |
| |
| /** |
| * Set of element names for which we should strip whitespace. |
| */ |
| Set stripSpace; |
| |
| /** |
| * Set of element names for which we should preserve whitespace. |
| */ |
| Set preserveSpace; |
| |
| /** |
| * Output options. |
| */ |
| Node output; |
| int outputMethod; |
| String outputVersion; |
| String outputEncoding; |
| boolean outputOmitXmlDeclaration; |
| boolean outputStandalone; |
| String outputPublicId; |
| String outputSystemId; |
| Collection outputCdataSectionElements; |
| boolean outputIndent; |
| String outputMediaType; |
| |
| /** |
| * Keys. |
| */ |
| Collection keys; |
| |
| /** |
| * Decimal formats. |
| */ |
| Map decimalFormats; |
| |
| /** |
| * Namespace aliases. |
| */ |
| Map namespaceAliases; |
| |
| /** |
| * Attribute-sets. |
| */ |
| List attributeSets; |
| |
| /** |
| * Variables. |
| */ |
| List variables; |
| |
| /** |
| * Variable and parameter bindings. |
| */ |
| Bindings bindings; |
| |
| /** |
| * Templates. |
| */ |
| LinkedList 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(); |
| excludeResultPrefixes = new HashSet(); |
| stripSpace = new LinkedHashSet(); |
| preserveSpace = new LinkedHashSet(); |
| outputCdataSectionElements = new LinkedHashSet(); |
| xpath = (XPathImpl) factory.xpathFactory.newXPath(); |
| xpath.setNamespaceContext(this); |
| if (parent == null) |
| { |
| bindings = new Bindings(this); |
| attributeSets = new LinkedList(); |
| variables = new LinkedList(); |
| namespaceAliases = new LinkedHashMap(); |
| templates = new LinkedList(); |
| keys = new LinkedList(); |
| decimalFormats = new LinkedHashMap(); |
| 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 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 (Iterator i = templates.iterator(); i.hasNext(); ) |
| { |
| Template t = (Template) i.next(); |
| 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 templates2 = new LinkedList(); |
| for (Iterator i = templates.iterator(); i.hasNext(); ) |
| { |
| Template t = (Template) i.next(); |
| templates2.add(t.clone(clone)); |
| } |
| clone.templates = templates2; |
| |
| LinkedList attributeSets2 = new LinkedList(); |
| for (Iterator i = attributeSets.iterator(); i.hasNext(); ) |
| { |
| AttributeSet as = (AttributeSet) i.next(); |
| attributeSets2.add(as.clone(clone)); |
| } |
| clone.attributeSets = attributeSets2; |
| |
| LinkedList variables2 = new LinkedList(); |
| for (Iterator i = variables.iterator(); i.hasNext(); ) |
| { |
| ParameterNode var = (ParameterNode) i.next(); |
| variables2.add(var.clone(clone)); |
| } |
| clone.variables = variables2; |
| |
| LinkedList keys2 = new LinkedList(); |
| for (Iterator i = keys.iterator(); i.hasNext(); ) |
| { |
| Key k = (Key) i.next(); |
| 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 topLevel = new ArrayList(variables); |
| Collections.sort(topLevel); |
| for (Iterator i = topLevel.iterator(); i.hasNext(); ) |
| { |
| ParameterNode var = (ParameterNode) i.next(); |
| 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 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 (Iterator j = templates.iterator(); j.hasNext(); ) |
| { |
| Template t = (Template) j.next(); |
| 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 (Iterator j = templates.iterator(); j.hasNext(); ) |
| { |
| Template t = (Template) j.next(); |
| 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 tokens = new ArrayList(); // text tokens |
| List types = new ArrayList(); // 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 = (String) tokens.get(i); |
| Boolean type = (Boolean) 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 (Iterator i = stripSpace.iterator(); i.hasNext(); ) |
| { |
| StrippingInstruction si = (StrippingInstruction) i.next(); |
| 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 (Iterator i = preserveSpace.iterator(); i.hasNext(); ) |
| { |
| StrippingInstruction si = (StrippingInstruction) i.next(); |
| 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 sortKeys = parseSortKeys(children); |
| List 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 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 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 parseSortKeys(Node node) |
| throws TransformerConfigurationException, XPathExpressionException |
| { |
| List ret = new LinkedList(); |
| 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.isEmpty() ? null : ret; |
| } |
| |
| final List parseWithParams(Node node) |
| throws TransformerConfigurationException, XPathExpressionException |
| { |
| List ret = new LinkedList(); |
| 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.isEmpty() ? null : 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 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); |
| } |
| |
| } |
| |