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