OSDN Git Service

libjava/ChangeLog:
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / xml / transform / Stylesheet.java
1 /* Stylesheet.java -- 
2    Copyright (C) 2004,2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38 package gnu.xml.transform;
39
40 import gnu.java.lang.CPStringBuilder;
41
42 import java.text.DecimalFormat;
43 import java.text.DecimalFormatSymbols;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.HashSet;
48 import java.util.Iterator;
49 import java.util.LinkedHashMap;
50 import java.util.LinkedHashSet;
51 import java.util.LinkedList;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Set;
55 import java.util.StringTokenizer;
56 import javax.xml.XMLConstants;
57 import javax.xml.namespace.NamespaceContext;
58 import javax.xml.namespace.QName;
59 import javax.xml.transform.Source;
60 import javax.xml.transform.TransformerConfigurationException;
61 import javax.xml.transform.TransformerException;
62 import javax.xml.xpath.XPathFunction;
63 import javax.xml.xpath.XPathFunctionResolver;
64 import javax.xml.xpath.XPathExpressionException;
65 import org.w3c.dom.Attr;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.DOMException;
68 import org.w3c.dom.Element;
69 import org.w3c.dom.NamedNodeMap;
70 import org.w3c.dom.Node;
71 import org.w3c.dom.Text;
72 import org.w3c.dom.UserDataHandler;
73 import gnu.xml.xpath.Expr;
74 import gnu.xml.xpath.NameTest;
75 import gnu.xml.xpath.NodeTypeTest;
76 import gnu.xml.xpath.Pattern;
77 import gnu.xml.xpath.Selector;
78 import gnu.xml.xpath.Root;
79 import gnu.xml.xpath.Test;
80 import gnu.xml.xpath.XPathImpl;
81
82 /**
83  * An XSL stylesheet.
84  *
85  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
86  */
87 class Stylesheet
88   implements NamespaceContext, XPathFunctionResolver, UserDataHandler, Cloneable
89 {
90
91   static final String XSL_NS = "http://www.w3.org/1999/XSL/Transform";
92   private static final NameTest STYLESHEET_PRESERVE_TEXT =
93     new NameTest(new QName(XSL_NS, "text"), false, false);
94   
95   static final int OUTPUT_XML = 0;
96   static final int OUTPUT_HTML = 1;
97   static final int OUTPUT_TEXT = 2;
98
99   final TransformerFactoryImpl factory;
100   TransformerImpl transformer;
101   Stylesheet parent;
102   final XPathImpl xpath;
103   final String systemId;
104   final int precedence;
105
106   final boolean debug;
107
108   /**
109    * Version of XSLT.
110    */
111   String version;
112
113   Collection<String> extensionElementPrefixes;
114   Collection<String> excludeResultPrefixes;
115
116   /**
117    * Set of element names for which we should strip whitespace.
118    */
119   Set<StrippingInstruction> stripSpace;
120
121   /**
122    * Set of element names for which we should preserve whitespace.
123    */
124   Set<StrippingInstruction> preserveSpace;
125
126   /**
127    * Output options.
128    */
129   Node output;
130   int outputMethod;
131   String outputVersion;
132   String outputEncoding;
133   boolean outputOmitXmlDeclaration;
134   boolean outputStandalone;
135   String outputPublicId;
136   String outputSystemId;
137   Collection<String> outputCdataSectionElements;
138   boolean outputIndent;
139   String outputMediaType;
140
141   /**
142    * Keys.
143    */
144   Collection<Key> keys;
145
146   /**
147    * Decimal formats.
148    */
149   Map<String,DecimalFormat> decimalFormats;
150   
151   /**
152    * Namespace aliases.
153    */
154   Map<String,String> namespaceAliases;
155
156   /**
157    * Attribute-sets.
158    */
159   List<AttributeSet> attributeSets;
160
161   /**
162    * Variables.
163    */
164   List<ParameterNode> variables;
165
166   /**
167    * Variable and parameter bindings.
168    */
169   Bindings bindings;
170
171   /**
172    * Templates.
173    */
174   LinkedList<Template> templates;
175
176   TemplateNode builtInNodeTemplate;
177   TemplateNode builtInTextTemplate;
178
179   /**
180    * Holds the current node while parsing.
181    * Necessary to associate the document function with its declaring node,
182    * to resolve namespaces, and to maintain the current node for the
183    * current() function.
184    */
185   Node current;
186
187   /**
188    * Set by a terminating message.
189    */
190   transient boolean terminated;
191
192   /**
193    * Current template in force.
194    */
195   transient Template currentTemplate;
196
197   Stylesheet(TransformerFactoryImpl factory,
198              Stylesheet parent,
199              Document doc,
200              String systemId,
201              int precedence)
202     throws TransformerConfigurationException
203   {
204     this.factory = factory;
205     this.systemId = systemId;
206     this.precedence = precedence;
207     this.parent = parent;
208     extensionElementPrefixes = new HashSet<String>();
209     excludeResultPrefixes = new HashSet<String>();
210     stripSpace = new LinkedHashSet<StrippingInstruction>();
211     preserveSpace = new LinkedHashSet<StrippingInstruction>();
212     outputCdataSectionElements = new LinkedHashSet<String>();
213     xpath = (XPathImpl) factory.xpathFactory.newXPath();
214     xpath.setNamespaceContext(this);
215     if (parent == null)
216       {
217         bindings = new Bindings(this);
218         attributeSets = new LinkedList<AttributeSet>();
219         variables = new LinkedList<ParameterNode>();
220         namespaceAliases = new LinkedHashMap<String,String>();
221         templates = new LinkedList<Template>();
222         keys = new LinkedList<Key>();
223         decimalFormats = new LinkedHashMap<String,DecimalFormat>();
224         initDefaultDecimalFormat();
225         xpath.setXPathFunctionResolver(this);
226       }
227     else
228       {
229         /* Test for import circularity */
230         for (Stylesheet ctx = this; ctx.parent != null; ctx = ctx.parent)
231           {
232             if (systemId != null && systemId.equals(ctx.parent.systemId))
233               {
234                 String msg = "circularity importing " + systemId;
235                 throw new TransformerConfigurationException(msg);
236               }
237           }
238         /* OK */
239         Stylesheet root = getRootStylesheet();
240         bindings = root.bindings;
241         attributeSets = root.attributeSets;
242         variables = root.variables;
243         namespaceAliases = root.namespaceAliases;
244         templates = root.templates;
245         keys = root.keys;
246         decimalFormats = root.decimalFormats;
247         xpath.setXPathFunctionResolver(root);
248       }
249     xpath.setXPathVariableResolver(bindings);
250
251     Test anyNode = new NodeTypeTest((short) 0);
252     List<Test> tests = Collections.singletonList(anyNode);
253     builtInNodeTemplate =
254       new ApplyTemplatesNode(new Selector(Selector.CHILD, tests),
255                              null, null, null, true);
256     builtInTextTemplate =
257       new ValueOfNode(new Selector(Selector.SELF, tests),
258                       false);
259     
260     parse(doc.getDocumentElement(), true);
261     current = doc; // Alow namespace resolution during processing
262     
263     debug = ("yes".equals(System.getProperty("xsl.debug")));
264
265     if (debug)
266       {
267         System.err.println("Stylesheet: " + doc.getDocumentURI());
268         for (Template t : templates)
269           {
270             t.list(System.err);
271             System.err.println("--------------------");
272           }
273       }
274   }
275
276   Stylesheet getRootStylesheet()
277   {
278     Stylesheet stylesheet = this;
279     while (stylesheet.parent != null)
280       stylesheet = stylesheet.parent;
281     return stylesheet;
282   }
283   
284   void initDefaultDecimalFormat()
285   {
286     DecimalFormat defaultDecimalFormat = new DecimalFormat();
287     DecimalFormatSymbols symbols = new DecimalFormatSymbols();
288     symbols.setDecimalSeparator('.');
289     symbols.setGroupingSeparator(',');
290     symbols.setPercent('%');
291     symbols.setPerMill('\u2030');
292     symbols.setZeroDigit('0');
293     symbols.setDigit('#');
294     symbols.setPatternSeparator(';');
295     symbols.setInfinity("Infinity");
296     symbols.setNaN("NaN");
297     symbols.setMinusSign('-');
298     defaultDecimalFormat.setDecimalFormatSymbols(symbols);
299     decimalFormats.put(null, defaultDecimalFormat);
300   }
301   
302   // -- Cloneable --
303
304   public Object clone()
305   {
306     try
307       {
308         Stylesheet clone = (Stylesheet) super.clone();
309         clone.bindings = (Bindings) bindings.clone();
310
311         LinkedList<Template> templates2 = new LinkedList<Template>();
312         for (Template t : templates)
313           {
314             templates2.add(t.clone(clone));
315           }
316         clone.templates = templates2;
317
318         LinkedList<AttributeSet> attributeSets2 = new LinkedList<AttributeSet>();
319         for (AttributeSet as : attributeSets)
320           {
321             attributeSets2.add(as.clone(clone));
322           }
323         clone.attributeSets = attributeSets2;
324
325         LinkedList<ParameterNode> variables2 = new LinkedList<ParameterNode>();
326         for (ParameterNode var : variables)
327           {
328             variables2.add(var.clone(clone));
329           }
330         clone.variables = variables2;
331
332         LinkedList<Key> keys2 = new LinkedList<Key>();
333         for (Key k : keys)
334           {
335             keys2.add(k.clone(clone));
336           }
337         clone.keys = keys2;
338         
339         return clone;
340       }
341     catch (CloneNotSupportedException e)
342       {
343         throw new Error(e.getMessage());
344       }
345   }
346
347   // -- Variable evaluation --
348
349   void initTopLevelVariables(Node context)
350     throws TransformerException
351   {
352     current = context;
353     // Sort the variables into order
354     // See XSLT 11.4: "If the template or expression specifying the value of
355     // a global variable x references a global variable y, then the value
356     // for y must be computed before the value of x."
357     List<ParameterNode> topLevel = new ArrayList<ParameterNode>(variables);
358     Collections.sort(topLevel);
359     for (ParameterNode var : topLevel)
360       {
361         bindings.set(var.name,
362                      var.getValue(this, null, context, 1, 1),
363                      var.type);
364       }
365     current = null;
366   }
367
368   // -- NamespaceContext --
369
370   public String getNamespaceURI(String prefix)
371   {
372     return (current == null) ? null : current.lookupNamespaceURI(prefix);
373   }
374
375   public String getPrefix(String namespaceURI)
376   {
377     return (current == null) ? null : current.lookupPrefix(namespaceURI);
378   }
379
380   public Iterator<String> getPrefixes(String namespaceURI)
381   {
382     // TODO
383     return Collections.singleton(getPrefix(namespaceURI)).iterator();
384   }
385
386   final QName getQName(String name)
387   {
388     String localName = name, uri = null, prefix = null;
389     int ci = name.indexOf(':');
390     if (ci != -1)
391       {
392         prefix = name.substring(0, ci);
393         localName = name.substring(ci + 1);
394         uri = getNamespaceURI(prefix);
395       }
396     return new QName(uri, localName, prefix);
397   }
398   
399   // -- Template selection --
400   
401   TemplateNode getTemplate(QName mode, Node context, boolean applyImports)
402     throws TransformerException
403   {
404     if (debug)
405       System.err.println("getTemplate: mode="+mode+" context="+context);
406     Template selected = null;
407     for (Template t : templates)
408       {
409         boolean isMatch = t.matches(mode, context);
410         if (applyImports)
411           {
412             if (currentTemplate == null)
413               {
414                 String msg = "current template may not be null " +
415                   "during apply-imports";
416                 throw new TransformerException(msg);
417               }
418             if (!currentTemplate.imports(t))
419               isMatch = false;
420           }
421         //System.err.println("\t"+context+" "+t+"="+isMatch);
422         if (isMatch)
423           {
424             // Conflict resolution
425             // @see http://www.w3.org/TR/xslt#conflict
426             if (selected == null)
427               selected = t;
428             else
429               {
430                 if (t.precedence < selected.precedence ||
431                     t.priority < selected.priority)
432                   continue;
433                 selected = t;
434               }
435           }
436       }
437     if (selected == null)
438       {
439         // Apply built-in template
440         // Current template is unchanged
441         if (debug)
442           System.err.println("\tbuiltInTemplate context="+context);
443         switch (context.getNodeType())
444           {
445           case Node.ELEMENT_NODE:
446           case Node.DOCUMENT_NODE:
447           case Node.DOCUMENT_FRAGMENT_NODE:
448           case Node.PROCESSING_INSTRUCTION_NODE:
449           case Node.COMMENT_NODE:
450             return builtInNodeTemplate;
451           case Node.TEXT_NODE:
452           case Node.CDATA_SECTION_NODE:
453           case Node.ATTRIBUTE_NODE:
454             return builtInTextTemplate;
455           default:
456             return null;
457           }
458       }
459     // Set current template
460     currentTemplate = selected;
461     if (debug)
462       System.err.println("\ttemplate="+currentTemplate+" context="+context);
463     return currentTemplate.node;
464   }
465
466   TemplateNode getTemplate(QName mode, QName name)
467     throws TransformerException
468   {
469     Template selected = null;
470     for (Template t : templates)
471       {
472         boolean isMatch = t.matches(name);
473         if (isMatch)
474           {
475             // Conflict resolution
476             // @see http://www.w3.org/TR/xslt#conflict
477             if (selected == null)
478               selected = t;
479             else
480               {
481                 if (t.precedence < selected.precedence ||
482                     t.priority < selected.priority)
483                   continue;
484                 selected = t;
485               }
486           }
487       }
488     if (selected == null)
489       return null;
490     return selected.node;
491   }
492
493   /**
494    * template
495    */
496   final Template parseTemplate(Node node, NamedNodeMap attrs)
497     throws TransformerConfigurationException, XPathExpressionException
498   {
499     String n = getAttribute(attrs, "name");
500     QName name = (n == null) ? null : getQName(n);
501     String m = getAttribute(attrs, "match");
502     Pattern match = null;
503     if (m != null)
504       {
505         try
506           {
507             match = (Pattern) xpath.compile(m);
508           }
509         catch (ClassCastException e)
510           {
511             String msg = "illegal pattern: " + m;
512             throw new TransformerConfigurationException(msg);
513           }
514       }
515     String p = getAttribute(attrs, "priority");
516     String mm = getAttribute(attrs, "mode");
517     QName mode = (mm == null) ? null : getQName(mm);
518     Node children = node.getFirstChild();
519     return new Template(this, name, match, parse(children),
520                         precedence, p, mode);
521   }
522
523   /**
524    * output
525    */
526   final void parseOutput(Node node, NamedNodeMap attrs)
527     throws TransformerConfigurationException
528   {
529     output = node;
530     String method = getAttribute(attrs, "method");
531     if ("xml".equals(method) || method == null)
532       outputMethod = OUTPUT_XML;
533     else if ("html".equals(method))
534       outputMethod = OUTPUT_HTML;
535     else if ("text".equals(method))
536       outputMethod = OUTPUT_TEXT;
537     else
538       {
539         String msg = "unsupported output method: " + method;
540         DOMSourceLocator l = new DOMSourceLocator(node);
541         throw new TransformerConfigurationException(msg, l);
542       }
543     outputPublicId = getAttribute(attrs, "doctype-public");
544     outputSystemId = getAttribute(attrs, "doctype-system");
545     outputEncoding = getAttribute(attrs, "encoding");
546     String indent = getAttribute(attrs, "indent");
547     if (indent != null)
548       outputIndent = "yes".equals(indent);
549     outputVersion = getAttribute(attrs, "version");
550     String omitXmlDecl = getAttribute(attrs, "omit-xml-declaration");
551     if (omitXmlDecl != null)
552       outputOmitXmlDeclaration = "yes".equals(omitXmlDecl);
553     String standalone = getAttribute(attrs, "standalone");
554     if (standalone != null)
555       outputStandalone = "yes".equals(standalone);
556     outputMediaType = getAttribute(attrs, "media-type");
557     String cdataSectionElements =
558       getAttribute(attrs, "cdata-section-elements");
559     if (cdataSectionElements != null)
560       {
561         StringTokenizer st = new StringTokenizer(cdataSectionElements, " ");
562         while (st.hasMoreTokens())
563           outputCdataSectionElements.add(st.nextToken());
564       }
565   }
566
567   /**
568    * key
569    */
570   final void parseKey(Node node, NamedNodeMap attrs)
571     throws TransformerConfigurationException, XPathExpressionException
572   {
573     String n = getRequiredAttribute(attrs, "name", node);
574     String m = getRequiredAttribute(attrs, "match", node);
575     String u = getRequiredAttribute(attrs, "use", node);
576     QName name = getQName(n);
577     Expr use = (Expr) xpath.compile(u);
578     try
579       {
580         Pattern match = (Pattern) xpath.compile(m);
581         Key key = new Key(name, match, use);
582         keys.add(key);
583       }
584     catch (ClassCastException e)
585       {
586         throw new TransformerConfigurationException("invalid pattern: " + m);
587       }
588   }
589
590   /**
591    * decimal-format
592    */
593   final void parseDecimalFormat(Node node, NamedNodeMap attrs)
594     throws TransformerConfigurationException
595   {
596     String dfName = getAttribute(attrs, "name");
597     DecimalFormat df = new DecimalFormat();
598     DecimalFormatSymbols symbols = new DecimalFormatSymbols();
599     symbols.setDecimalSeparator(parseDFChar(attrs, "decimal-separator", '.'));
600     symbols.setGroupingSeparator(parseDFChar(attrs, "grouping-separator", ','));
601     symbols.setInfinity(parseDFString(attrs, "infinity", "Infinity"));
602     symbols.setMinusSign(parseDFChar(attrs, "minus-sign", '-'));
603     symbols.setNaN(parseDFString(attrs, "NaN", "NaN"));
604     symbols.setPercent(parseDFChar(attrs, "percent", '%'));
605     symbols.setPerMill(parseDFChar(attrs, "per-mille", '\u2030'));
606     symbols.setZeroDigit(parseDFChar(attrs, "zero-digit", '0'));
607     symbols.setDigit(parseDFChar(attrs, "digit", '#'));
608     symbols.setPatternSeparator(parseDFChar(attrs, "pattern-separator", ';'));
609     df.setDecimalFormatSymbols(symbols);
610     decimalFormats.put(dfName, df);
611   }
612
613   private final char parseDFChar(NamedNodeMap attrs, String name, char def)
614     throws TransformerConfigurationException
615   {
616     Node attr = attrs.getNamedItem(name);
617     try
618       {
619         return (attr == null) ? def : attr.getNodeValue().charAt(0);
620       }
621     catch (StringIndexOutOfBoundsException e)
622       {
623         throw new TransformerConfigurationException("empty attribute '" +
624                                                     name +
625                                                     "' in decimal-format", e);
626       }
627   }
628
629   private final String parseDFString(NamedNodeMap attrs, String name,
630                                      String def)
631   {
632     Node attr = attrs.getNamedItem(name);
633     return (attr == null) ? def : attr.getNodeValue();
634   }
635   
636   /**
637    * namespace-alias
638    */
639   final void parseNamespaceAlias(Node node, NamedNodeMap attrs)
640     throws TransformerConfigurationException
641   {
642     String sp = getRequiredAttribute(attrs, "stylesheet-prefix", node);
643     String rp = getRequiredAttribute(attrs, "result-prefix", node);
644     namespaceAliases.put(sp, rp);
645   }
646
647   /**
648    * attribute-set
649    */
650   final void parseAttributeSet(Node node, NamedNodeMap attrs)
651     throws TransformerConfigurationException, XPathExpressionException
652   {
653     TemplateNode children = parse(node.getFirstChild());
654     String name = getRequiredAttribute(attrs, "name", node);
655     String uas = getAttribute(attrs, "use-attribute-sets");
656     attributeSets.add(new AttributeSet(children, name, uas));
657   }
658
659   /**
660    * Parse top-level elements.
661    */
662   void parse(Node node, boolean root)
663     throws TransformerConfigurationException
664   {
665     while (node != null)
666       {
667         current = node;
668         doParse(node, root);
669         node = node.getNextSibling();
670       }
671   }
672
673   void doParse(Node node, boolean root)
674     throws TransformerConfigurationException
675   {
676     try
677       {
678         String namespaceUri = node.getNamespaceURI();
679         if (XSL_NS.equals(namespaceUri) &&
680             node.getNodeType() == Node.ELEMENT_NODE)
681           {
682             String name = node.getLocalName();
683             NamedNodeMap attrs = node.getAttributes();
684             if ("stylesheet".equals(name))
685               {
686                 version = getAttribute(attrs, "version");
687                 String eep = getAttribute(attrs, "extension-element-prefixes");
688                 if (eep != null)
689                   {
690                     StringTokenizer st = new StringTokenizer(eep);
691                     while (st.hasMoreTokens())
692                       {
693                         extensionElementPrefixes.add(st.nextToken());
694                       }
695                   }
696                 String erp = getAttribute(attrs, "exclude-result-prefixes");
697                 if (erp != null)
698                   {
699                     StringTokenizer st = new StringTokenizer(erp);
700                     while (st.hasMoreTokens())
701                       {
702                         excludeResultPrefixes.add(st.nextToken());
703                       }
704                   }
705                 parse(node.getFirstChild(), false);
706               }
707             else if ("template".equals(name))
708               templates.add(parseTemplate(node, attrs));
709             else if ("param".equals(name) ||
710                      "variable".equals(name))
711               {
712                 int type = "variable".equals(name) ?
713                   Bindings.VARIABLE : Bindings.PARAM;
714                 TemplateNode content = parse(node.getFirstChild());
715                 QName paramName =
716                   getQName(getRequiredAttribute(attrs, "name", node));
717                 String select = getAttribute(attrs, "select");
718                 ParameterNode param;
719                 if (select != null && select.length() > 0)
720                   {
721                     if (content != null)
722                       {
723                         String msg = "parameter '" + paramName +
724                           "' has both select and content";
725                         DOMSourceLocator l = new DOMSourceLocator(node);
726                         throw new TransformerConfigurationException(msg, l);
727                       }
728                     Expr expr = (Expr) xpath.compile(select);
729                     param = new ParameterNode(paramName, expr, type);
730                   }
731                 else
732                   {
733                     param = new ParameterNode(paramName, null, type);
734                     param.children = content;
735                   }
736                 variables.add(param);
737               }
738             else if ("include".equals(name) || "import".equals(name))
739               {
740                 int delta = "import".equals(name) ? -1 : 0;
741                 String href = getRequiredAttribute(attrs, "href", node);
742                 Source source;
743                 synchronized (factory.resolver)
744                   {
745                     if (transformer != null)
746                       {
747                         factory.resolver
748                           .setUserResolver(transformer.getURIResolver());
749                         factory.resolver
750                           .setUserListener(transformer.getErrorListener());
751                       }
752                     source = factory.resolver.resolve(systemId, href);
753                   }
754                 factory.newStylesheet(source, precedence + delta, this);
755               }
756             else if ("output".equals(name))
757               parseOutput(node, attrs);
758             else if ("preserve-space".equals(name))
759               {
760                 String elements =
761                   getRequiredAttribute(attrs, "elements", node);
762                 StringTokenizer st = new StringTokenizer(elements,
763                                                          " \t\n\r");
764                 while (st.hasMoreTokens())
765                   {
766                     NameTest element = parseNameTest(st.nextToken());
767                     preserveSpace.add(new StrippingInstruction(element,
768                                                                precedence));
769                   }
770               }
771             else if ("strip-space".equals(name))
772               {
773                 String elements =
774                   getRequiredAttribute(attrs, "elements", node);
775                 StringTokenizer st = new StringTokenizer(elements,
776                                                          " \t\n\r");
777                 while (st.hasMoreTokens())
778                   {
779                     NameTest element = parseNameTest(st.nextToken());
780                     stripSpace.add(new StrippingInstruction(element, 
781                                                             precedence));
782                   }
783               }
784             else if ("key".equals(name))
785               parseKey(node, attrs);
786             else if ("decimal-format".equals(name))
787               parseDecimalFormat(node, attrs);
788             else if ("namespace-alias".equals(name))
789               parseNamespaceAlias(node, attrs);
790             else if ("attribute-set".equals(name))
791               parseAttributeSet(node, attrs);
792           }
793         else if (root)
794           {
795             // Literal document element
796             Attr versionNode =
797               ((Element)node).getAttributeNodeNS(XSL_NS, "version");
798             if (versionNode == null)
799               {
800                 String msg = "no xsl:version attribute on literal result node";
801                 DOMSourceLocator l = new DOMSourceLocator(node);
802                 throw new TransformerConfigurationException(msg, l);
803               }
804             version = versionNode.getValue();
805             Node rootClone = node.cloneNode(true);
806             NamedNodeMap attrs = rootClone.getAttributes();
807             attrs.removeNamedItemNS(XSL_NS, "version");
808             templates.add(new Template(this, null, new Root(),
809                                        parse(rootClone),
810                                        precedence,
811                                        null,
812                                        null));
813           }
814         else
815           {
816             // Skip unknown elements, text, comments, etc
817           }
818       }
819     catch (TransformerException e)
820       {
821         DOMSourceLocator l = new DOMSourceLocator(node);
822         throw new TransformerConfigurationException(e.getMessage(), l, e);
823       }
824     catch (DOMException e)
825       {
826         DOMSourceLocator l = new DOMSourceLocator(node);
827         throw new TransformerConfigurationException(e.getMessage(), l, e);
828       }
829     catch (XPathExpressionException e)
830       {
831         DOMSourceLocator l = new DOMSourceLocator(node);
832         throw new TransformerConfigurationException(e.getMessage(), l, e);
833       }
834   }
835
836   final NameTest parseNameTest(String token)
837   {
838     if ("*".equals(token))
839       return new NameTest(null, true, true);
840     else if (token.endsWith(":*"))
841       {
842         QName qName = getQName(token);
843         return new NameTest(qName, true, false);
844       }
845     else
846       {
847         QName qName = getQName(token);
848         return new NameTest(qName, false, false);
849       }
850   }
851
852   final TemplateNode parseAttributeValueTemplate(String value, Node source)
853     throws TransformerConfigurationException, XPathExpressionException
854   {
855     current = source;
856     // Tokenize
857     int len = value.length();
858     int off = 0;
859     List<String> tokens = new ArrayList<String>(); // text tokens
860     List<Boolean> types = new ArrayList<Boolean>(); // literal or expression
861     int depth = 0;
862     for (int i = 0; i < len; i++)
863       {
864         char c = value.charAt(i);
865         if (c == '{')
866           {
867             if (i < (len - 1) && value.charAt(i + 1) == '{')
868               {
869                 tokens.add(value.substring(off, i + 1));
870                 types.add(Boolean.FALSE);
871                 i++;
872                 off = i + 1;
873                 continue;
874               }
875             if (depth == 0)
876               {
877                 if (i - off > 0)
878                   {
879                     tokens.add(value.substring(off, i));
880                     types.add(Boolean.FALSE);
881                   }
882                 off = i + 1;
883               }
884             depth++;
885           }
886         else if (c == '}')
887           {
888             if (i < (len - 1) && value.charAt(i + 1) == '}')
889               {
890                 tokens.add(value.substring(off, i + 1));
891                 types.add(Boolean.FALSE);
892                 i++;
893                 off = i + 1;
894                 continue;
895               }
896             if (depth == 1)
897               {
898                 if (i - off > 0)
899                   {
900                     tokens.add(value.substring(off, i));
901                     types.add(Boolean.TRUE);
902                   }
903                 else
904                   {
905                     String msg = "attribute value template " +
906                       "must contain expression: " + value;
907                     DOMSourceLocator l = new DOMSourceLocator(source);
908                     throw new TransformerConfigurationException(msg, l);
909                   }
910                 off = i + 1;
911               }
912             depth--;
913           }
914       }
915     if (depth > 0)
916       {
917         String msg = "invalid attribute value template: " + value;
918         throw new TransformerConfigurationException(msg);
919       }
920     if (len - off > 0)
921       {
922         // Trailing text
923         tokens.add(value.substring(off));
924         types.add(Boolean.FALSE);
925       }
926     
927     // Construct template node tree
928     TemplateNode ret = null;
929     Document doc = source.getOwnerDocument();
930     len = tokens.size();
931     for (int i = len - 1; i >= 0; i--)
932       {
933         String token = tokens.get(i);
934         Boolean type = types.get(i);
935         if (type == Boolean.TRUE)
936           {
937             // Expression text
938             Expr select = (Expr) xpath.compile(token);
939             TemplateNode ret2 = new ValueOfNode(select, false);
940             ret2.next = ret;
941             ret = ret2;
942           }
943         else
944           {
945             // Verbatim text
946             TemplateNode ret2 = new LiteralNode(doc.createTextNode(token));
947             ret2.next = ret;
948             ret = ret2;
949           }
950       }
951     return ret;
952   }
953
954   /**
955    * Whitespace stripping.
956    * @param text the text node
957    * @param source true if a source node, false if a stylesheet text node
958    * @see http://www.w3.org/TR/xslt#strip
959    */
960   boolean isPreserved(Text text, boolean source)
961     throws TransformerConfigurationException
962   {
963     // Check characters in text
964     String value = text.getData();
965     if (value != null)
966       {
967         int len = value.length();
968         for (int i = 0; i < len; i++)
969           {
970             char c = value.charAt(i);
971             if (c != 0x20 && c != 0x09 && c != 0x0a && c != 0x0d)
972               return true;
973           }
974       }
975     // Check parent node
976     Node ctx = text.getParentNode();
977     if (source)
978       {
979         // Source document text node
980         boolean preserve = true;
981         float psPriority = 0.0f, ssPriority = 0.0f;
982         if (!stripSpace.isEmpty())
983           {
984             // Conflict resolution
985             StrippingInstruction ssi = null, psi = null;
986             for (StrippingInstruction si : stripSpace)
987               {
988                 if (si.element.matches(ctx, 1, 1))
989                   {
990                     if (ssi != null)
991                       {
992                         if (si.precedence < ssi.precedence)
993                           continue;
994                         float p = si.getPriority();
995                         if (p < ssPriority)
996                           continue;
997                       }
998                     ssi = si;
999                   }
1000               }
1001             for (StrippingInstruction si : preserveSpace)
1002               {
1003                 if (si.element.matches(ctx, 1, 1))
1004                   {
1005                     if (psi != null)
1006                       {
1007                         if (si.precedence < psi.precedence)
1008                           continue;
1009                         float p = si.getPriority();
1010                         if (p < psPriority)
1011                           continue;
1012                       }
1013                     psi = si;
1014                   }
1015               }
1016             if (ssi != null)
1017               {
1018                 if (psi != null)
1019                   {
1020                     if (psi.precedence < ssi.precedence)
1021                       preserve = false;
1022                     else if (psPriority < ssPriority)
1023                       preserve = false;
1024                   }
1025                 else
1026                   preserve = false;
1027               }
1028           }
1029         if (preserve)
1030           return true;
1031       }
1032     else
1033       {
1034         // Stylesheet text node
1035         if (STYLESHEET_PRESERVE_TEXT.matches(ctx, 1, 1))
1036           return true;
1037       }
1038     // Check whether any ancestor specified xml:space
1039     while (ctx != null)
1040       {
1041         if (ctx.getNodeType() == Node.ELEMENT_NODE)
1042           {
1043             Element element = (Element) ctx;
1044             String xmlSpace = element.getAttribute("xml:space");
1045             if ("default".equals(xmlSpace))
1046               break;
1047             else if ("preserve".equals(xmlSpace))
1048               return true;
1049             else if (xmlSpace.length() > 0)
1050               {
1051                 String msg = "Illegal value for xml:space: " + xmlSpace;
1052                 throw new TransformerConfigurationException(msg);
1053               }
1054           }
1055         ctx = ctx.getParentNode();
1056       }
1057     return false;
1058   }
1059
1060   public XPathFunction resolveFunction(QName name, int arity)
1061   {
1062     String uri = name.getNamespaceURI();
1063     if (XSL_NS.equals(uri) || uri == null || uri.length() == 0)
1064       {
1065         String localName = name.getLocalPart();
1066         if ("document".equals(localName) && (arity == 1 || arity == 2))
1067           {
1068             if (current == null)
1069                 throw new RuntimeException("current is null");
1070             return new DocumentFunction(getRootStylesheet(), current);
1071           }
1072         else if ("key".equals(localName) && (arity == 2))
1073           return new KeyFunction(getRootStylesheet());
1074         else if ("format-number".equals(localName) &&
1075                  (arity == 2 || arity == 3))
1076           return new FormatNumberFunction(getRootStylesheet());
1077         else if ("current".equals(localName) && (arity == 0))
1078           return new CurrentFunction(getRootStylesheet());
1079         else if ("unparsed-entity-uri".equals(localName) && (arity == 1))
1080           return new UnparsedEntityUriFunction();
1081         else if ("generate-id".equals(localName) &&
1082                  (arity == 1 || arity == 0))
1083           return new GenerateIdFunction();
1084         else if ("system-property".equals(localName) && (arity == 1))
1085           return new SystemPropertyFunction();
1086         else if ("element-available".equals(localName) && (arity == 1))
1087           return new ElementAvailableFunction(new NamespaceProxy(current));
1088         else if ("function-available".equals(localName) && (arity == 1))
1089           return new FunctionAvailableFunction(new NamespaceProxy(current));
1090       }
1091     return null;
1092   }
1093   
1094   // -- Parsing --
1095
1096   /**
1097    * apply-templates
1098    */
1099   final TemplateNode parseApplyTemplates(Node node)
1100     throws TransformerConfigurationException, XPathExpressionException
1101   {
1102     NamedNodeMap attrs = node.getAttributes();
1103     String m = getAttribute(attrs, "mode");
1104     QName mode = (m == null) ? null : getQName(m);
1105     String s = getAttribute(attrs, "select");
1106     if (s == null)
1107       s = "child::node()";
1108     Node children = node.getFirstChild();
1109     List<SortKey> sortKeys = parseSortKeys(children);
1110     List<WithParam> withParams = parseWithParams(children);
1111     Expr select = (Expr) xpath.compile(s);
1112     return new ApplyTemplatesNode(select, mode,
1113                                   sortKeys, withParams, false);
1114   }
1115
1116   /**
1117    * call-template
1118    */
1119   final TemplateNode parseCallTemplate(Node node)
1120     throws TransformerConfigurationException, XPathExpressionException
1121   {
1122     NamedNodeMap attrs = node.getAttributes();
1123     String n = getRequiredAttribute(attrs, "name", node);
1124     QName name = getQName(n);
1125     Node children = node.getFirstChild();
1126     List<WithParam> withParams = parseWithParams(children);
1127     return new CallTemplateNode(name, withParams);
1128   }
1129   
1130   /**
1131    * value-of
1132    */
1133   final TemplateNode parseValueOf(Node node)
1134     throws TransformerConfigurationException, XPathExpressionException
1135   {
1136     NamedNodeMap attrs = node.getAttributes();
1137     String s = getRequiredAttribute(attrs, "select", node);
1138     String doe = getAttribute(attrs, "disable-output-escaping");
1139     boolean d = "yes".equals(doe);
1140     Expr select = (Expr) xpath.compile(s);
1141     return new ValueOfNode(select, d);
1142   }
1143   
1144   /**
1145    * for-each
1146    */
1147   final TemplateNode parseForEach(Node node)
1148     throws TransformerConfigurationException, XPathExpressionException
1149   {
1150     NamedNodeMap attrs = node.getAttributes();
1151     String s = getRequiredAttribute(attrs, "select", node);
1152     Node children = node.getFirstChild();
1153     List<SortKey> sortKeys = parseSortKeys(children);
1154     Expr select = (Expr) xpath.compile(s);
1155     ForEachNode ret = new ForEachNode(select, sortKeys);
1156     ret.children = parse(children);
1157     return ret;
1158   }
1159   
1160   /**
1161    * if
1162    */
1163   final TemplateNode parseIf(Node node)
1164     throws TransformerConfigurationException, XPathExpressionException
1165   {
1166     NamedNodeMap attrs = node.getAttributes();
1167     String t = getRequiredAttribute(attrs, "test", node);
1168     Expr test = (Expr) xpath.compile(t);
1169     Node children = node.getFirstChild();
1170     IfNode ret = new IfNode(test);
1171     ret.children = parse(children);
1172     return ret;
1173   }
1174   
1175   /**
1176    * when
1177    */
1178   final TemplateNode parseWhen(Node node)
1179     throws TransformerConfigurationException, XPathExpressionException
1180   {
1181     NamedNodeMap attrs = node.getAttributes();
1182     String t = getRequiredAttribute(attrs, "test", node);
1183     Expr test = (Expr) xpath.compile(t);
1184     Node children = node.getFirstChild();
1185     WhenNode ret = new WhenNode(test);
1186     ret.children = parse(children);
1187     return ret;
1188   }
1189   
1190   /**
1191    * element
1192    */
1193   final TemplateNode parseElement(Node node)
1194     throws TransformerConfigurationException, XPathExpressionException
1195   {
1196     NamedNodeMap attrs = node.getAttributes();
1197     String name = getRequiredAttribute(attrs, "name", node);
1198     String namespace = getAttribute(attrs, "namespace");
1199     String uas = getAttribute(attrs, "use-attribute-sets");
1200     TemplateNode n = parseAttributeValueTemplate(name, node);
1201     TemplateNode ns = (namespace == null) ? null :
1202       parseAttributeValueTemplate(namespace, node);
1203     Node children = node.getFirstChild();
1204     ElementNode ret = new ElementNode(n, ns, uas, node);
1205     ret.children = parse(children);
1206     return ret;
1207   }
1208
1209   /**
1210    * attribute
1211    */
1212   final TemplateNode parseAttribute(Node node)
1213     throws TransformerConfigurationException, XPathExpressionException
1214   {
1215     NamedNodeMap attrs = node.getAttributes();
1216     String name = getRequiredAttribute(attrs, "name", node);
1217     String namespace = getAttribute(attrs, "namespace");
1218     TemplateNode n = parseAttributeValueTemplate(name, node);
1219     TemplateNode ns = (namespace == null) ? null :
1220       parseAttributeValueTemplate(namespace, node);
1221     Node children = node.getFirstChild();
1222     AttributeNode ret = new AttributeNode(n, ns, node);
1223     ret.children = parse(children);
1224     return ret;
1225   }
1226   
1227   /**
1228    * text
1229    */
1230   final TemplateNode parseText(Node node)
1231     throws TransformerConfigurationException, XPathExpressionException
1232   {
1233     NamedNodeMap attrs = node.getAttributes();
1234     String doe = getAttribute(attrs, "disable-output-escaping");
1235     boolean d = "yes".equals(doe);
1236     Node children = node.getFirstChild();
1237     TextNode ret = new TextNode(d);
1238     ret.children = parse(children);
1239     return ret;
1240   }
1241   
1242   /**
1243    * copy
1244    */
1245   final TemplateNode parseCopy(Node node)
1246     throws TransformerConfigurationException, XPathExpressionException
1247   {
1248     NamedNodeMap attrs = node.getAttributes();
1249     String uas = getAttribute(attrs, "use-attribute-sets");
1250     Node children = node.getFirstChild();
1251     CopyNode ret = new CopyNode(uas);
1252     ret.children = parse(children);
1253     return ret;
1254   }
1255   
1256   /**
1257    * processing-instruction
1258    */
1259   final TemplateNode parseProcessingInstruction(Node node)
1260     throws TransformerConfigurationException, XPathExpressionException
1261   {
1262     NamedNodeMap attrs = node.getAttributes();
1263     String name = getRequiredAttribute(attrs, "name", node);
1264     Node children = node.getFirstChild();
1265     ProcessingInstructionNode ret = new ProcessingInstructionNode(name);
1266     ret.children = parse(children);
1267     return ret;
1268   }
1269   
1270   /**
1271    * number
1272    */
1273   final TemplateNode parseNumber(Node node)
1274     throws TransformerConfigurationException, XPathExpressionException
1275   {
1276     NamedNodeMap attrs = node.getAttributes();
1277     String v = getAttribute(attrs, "value");
1278     String ff = getAttribute(attrs, "format");
1279     if (ff == null)
1280       {
1281         ff = "1";
1282       }
1283     TemplateNode format = parseAttributeValueTemplate(ff, node);
1284     String lang = getAttribute(attrs, "lang");
1285     String lv = getAttribute(attrs, "letter-value");
1286     int letterValue = "traditional".equals(lv) ?
1287       AbstractNumberNode.TRADITIONAL :
1288       AbstractNumberNode.ALPHABETIC;
1289     String gs = getAttribute(attrs, "grouping-separator");
1290     String gz = getAttribute(attrs, "grouping-size");
1291     int gz2 = (gz != null && gz.length() > 0) ?
1292       Integer.parseInt(gz) : 1;
1293     Node children = node.getFirstChild();
1294     TemplateNode ret;
1295     if (v != null && v.length() > 0)
1296       {
1297         Expr value = (Expr) xpath.compile(v);
1298         ret = new NumberNode(value, format, lang,
1299                              letterValue, gs, gz2);
1300       }
1301     else
1302       {
1303         String l = getAttribute(attrs, "level");
1304         int level =
1305           "multiple".equals(l) ? NodeNumberNode.MULTIPLE :
1306                       "any".equals(l) ? NodeNumberNode.ANY :
1307                       NodeNumberNode.SINGLE;
1308         String c = getAttribute(attrs, "count");
1309         String f = getAttribute(attrs, "from");
1310         Pattern count = null;
1311         Pattern from = null;
1312         if (c != null)
1313           {
1314             try
1315               {
1316                 count = (Pattern) xpath.compile(c);
1317               }
1318             catch (ClassCastException e)
1319               {
1320                 String msg = "invalid pattern: " + c;
1321                 throw new TransformerConfigurationException(msg);
1322               }
1323           }
1324         if (f != null)
1325           {
1326             try
1327               {
1328                 from = (Pattern) xpath.compile(f);
1329               }
1330             catch (ClassCastException e)
1331               {
1332                 String msg = "invalid pattern: " + f;
1333                 throw new TransformerConfigurationException(msg);
1334               }
1335           }
1336         ret = new NodeNumberNode(level, count, from,
1337                                  format, lang,
1338                                  letterValue, gs, gz2);
1339       }
1340     ret.children = parse(children);
1341     return ret;
1342   }
1343   
1344   /**
1345    * copy-of
1346    */
1347   final TemplateNode parseCopyOf(Node node)
1348     throws TransformerConfigurationException, XPathExpressionException
1349   {
1350     NamedNodeMap attrs = node.getAttributes();
1351     String s = getRequiredAttribute(attrs, "select", node);
1352     Expr select = (Expr) xpath.compile(s);
1353     Node children = node.getFirstChild();
1354     CopyOfNode ret = new CopyOfNode(select);
1355     ret.children = parse(children);
1356     return ret;
1357   }
1358   
1359   /**
1360    * message
1361    */
1362   final TemplateNode parseMessage(Node node)
1363     throws TransformerConfigurationException, XPathExpressionException
1364   {
1365     NamedNodeMap attrs = node.getAttributes();
1366     String t = getAttribute(attrs, "terminate");
1367     boolean terminate = "yes".equals(t);
1368     Node children = node.getFirstChild();
1369     MessageNode ret = new MessageNode(terminate);
1370     ret.children = parse(children);
1371     return ret;
1372   }
1373   
1374   /**
1375    * Parse template-level elements.
1376    */
1377   final TemplateNode parse(Node node)
1378     throws TransformerConfigurationException
1379   {
1380     TemplateNode first = null;
1381     TemplateNode previous = null;
1382     while (node != null)
1383       {
1384         Node next = node.getNextSibling();
1385         TemplateNode tnode = doParse(node);
1386         if (tnode != null)
1387           {
1388             if (first == null)
1389               first = tnode;
1390             if (previous != null)
1391               previous.next = tnode;
1392             previous = tnode;
1393           }
1394         node = next;
1395       }
1396     return first;
1397   }
1398
1399   private final TemplateNode doParse(Node node)
1400     throws TransformerConfigurationException
1401   {
1402     // Hack to associate the document function with its declaring node
1403     current = node;
1404     try
1405       {
1406         String namespaceUri = node.getNamespaceURI();
1407         if (Stylesheet.XSL_NS.equals(namespaceUri) &&
1408             Node.ELEMENT_NODE == node.getNodeType())
1409           {
1410             String name = node.getLocalName();
1411             if ("apply-templates".equals(name))
1412               return parseApplyTemplates(node);
1413             else if ("call-template".equals(name))
1414               return parseCallTemplate(node);
1415             else if ("value-of".equals(name))
1416               return parseValueOf(node);
1417             else if ("for-each".equals(name))
1418               return parseForEach(node);
1419             else if ("if".equals(name))
1420               return parseIf(node);
1421             else if ("choose".equals(name))
1422               {
1423                 Node children = node.getFirstChild();
1424                 ChooseNode ret = new ChooseNode();
1425                 ret.children = parse(children);
1426                 return ret;
1427               }
1428             else if ("when".equals(name))
1429               return parseWhen(node);
1430             else if ("otherwise".equals(name))
1431               {
1432                 Node children = node.getFirstChild();
1433                 OtherwiseNode ret = new OtherwiseNode();
1434                 ret.children = parse(children);
1435                 return ret;
1436               }
1437             else if ("element".equals(name))
1438               return parseElement(node);
1439             else if ("attribute".equals(name))
1440               return parseAttribute(node);
1441             else if ("text".equals(name))
1442               return parseText(node);
1443             else if ("copy".equals(name))
1444               return parseCopy(node);
1445             else if ("processing-instruction".equals(name))
1446               return parseProcessingInstruction(node);
1447             else if ("comment".equals(name))
1448               {
1449                 Node children = node.getFirstChild();
1450                 CommentNode ret = new CommentNode();
1451                 ret.children = parse(children);
1452                 return ret;
1453               }
1454             else if ("number".equals(name))
1455               return parseNumber(node);
1456             else if ("param".equals(name) ||
1457                      "variable".equals(name))
1458               {
1459                 int type = "variable".equals(name) ?
1460                   Bindings.VARIABLE : Bindings.PARAM;
1461                 NamedNodeMap attrs = node.getAttributes();
1462                 Node children = node.getFirstChild();
1463                 TemplateNode content = parse(children);
1464                 QName paramName = 
1465                   getQName(getRequiredAttribute(attrs, "name", node));
1466                 String select = getAttribute(attrs, "select");
1467                 ParameterNode ret;
1468                 if (select != null)
1469                   {
1470                     if (content != null)
1471                       {
1472                         String msg = "parameter '" + paramName +
1473                           "' has both select and content";
1474                         DOMSourceLocator l = new DOMSourceLocator(node);
1475                         throw new TransformerConfigurationException(msg, l);
1476                       }
1477                     Expr expr = (Expr) xpath.compile(select);
1478                     ret = new ParameterNode(paramName, expr, type);
1479                   }
1480                 else
1481                   {
1482                     ret = new ParameterNode(paramName, null, type);
1483                     ret.children = content;
1484                   }
1485                 return ret;
1486               }
1487             else if ("copy-of".equals(name))
1488               return parseCopyOf(node);
1489             else if ("message".equals(name))
1490               return parseMessage(node);
1491             else if ("apply-imports".equals(name))
1492               {
1493                 Node children = node.getFirstChild();
1494                 ApplyImportsNode ret = new ApplyImportsNode();
1495                 ret.children = parse(children);
1496                 return ret;
1497               }
1498             else
1499               {
1500                 // xsl:fallback
1501                 // Pass over any other XSLT nodes
1502                 return null;
1503               }
1504           }
1505         String prefix = node.getPrefix();
1506         if (extensionElementPrefixes.contains(prefix))
1507           {
1508             // Check for xsl:fallback
1509             for (Node ctx = node.getFirstChild(); ctx != null;
1510                  ctx = ctx.getNextSibling())
1511               {
1512                 String ctxUri = ctx.getNamespaceURI();
1513                 if (XSL_NS.equals(ctxUri) &&
1514                     "fallback".equals(ctx.getLocalName()))
1515                   {
1516                     ctx = ctx.getFirstChild();
1517                     return (ctx == null) ? null : parse(ctx);
1518                   }
1519               }
1520             // Otherwise pass over extension element
1521             return null;
1522           }
1523         switch (node.getNodeType())
1524           {
1525           case Node.TEXT_NODE:
1526           case Node.CDATA_SECTION_NODE:
1527             // Determine whether to strip whitespace
1528             Text text = (Text) node;
1529             if (!isPreserved(text, false))
1530               {
1531                 // Strip
1532                 text.getParentNode().removeChild(text);
1533                 return null;
1534               }
1535             break;
1536           case Node.COMMENT_NODE:
1537             // Ignore comments
1538             return null;
1539           case Node.ELEMENT_NODE:
1540             // Check for attribute value templates and use-attribute-sets
1541             NamedNodeMap attrs = node.getAttributes();
1542             boolean convert = false;
1543             String useAttributeSets = null;
1544             int len = attrs.getLength();
1545             for (int i = 0; i < len; i++)
1546               {
1547                 Node attr = attrs.item(i);
1548                 String value = attr.getNodeValue();
1549                 if (Stylesheet.XSL_NS.equals(attr.getNamespaceURI()) &&
1550                     "use-attribute-sets".equals(attr.getLocalName()))
1551                   {
1552                     useAttributeSets = value;
1553                     convert = true;
1554                     break;
1555                   }
1556                 int start = value.indexOf('{');
1557                 int end = value.indexOf('}');
1558                 if (start != -1 || end != -1)
1559                   {
1560                     convert = true;
1561                     break;
1562                   }
1563               }
1564             if (convert)
1565               {
1566                 // Create an element-producing template node instead
1567                 // with appropriate attribute-producing child template nodes
1568                 Node children = node.getFirstChild();
1569                 TemplateNode child = parse(children);
1570                 for (int i = 0; i < len; i++)
1571                   {
1572                     Node attr = attrs.item(i);
1573                     String ans = attr.getNamespaceURI();
1574                     String aname = attr.getNodeName();
1575                     if (Stylesheet.XSL_NS.equals(ans) &&
1576                         "use-attribute-sets".equals(attr.getLocalName()))
1577                       continue;
1578                     String value = attr.getNodeValue();
1579                     TemplateNode grandchild =
1580                       parseAttributeValueTemplate(value, node);
1581                     TemplateNode n =
1582                       parseAttributeValueTemplate(aname, node);
1583                     TemplateNode ns = (ans == null) ? null :
1584                       parseAttributeValueTemplate(ans, node);
1585                     TemplateNode newChild = new AttributeNode(n, ns, attr);
1586                     newChild.children = grandchild;
1587                     newChild.next = child;
1588                     child = newChild;
1589                   }
1590                 String ename = node.getNodeName();
1591                 TemplateNode n = parseAttributeValueTemplate(ename, node);
1592                 //TemplateNode ns = (namespaceUri == null) ? null :
1593                 //  parseAttributeValueTemplate(namespaceUri, node);
1594                 TemplateNode ns = null;
1595                 ElementNode ret = new ElementNode(n, ns, useAttributeSets,
1596                                                   node);
1597                 ret.children = child;
1598                 return ret;
1599               }
1600             // Otherwise fall through
1601             break;
1602           }
1603       }
1604     catch (XPathExpressionException e)
1605       {
1606         DOMSourceLocator l = new DOMSourceLocator(node);
1607         throw new TransformerConfigurationException(e.getMessage(), l, e);
1608       }
1609     Node children = node.getFirstChild();
1610     LiteralNode ret = new LiteralNode(node);
1611     ret.children = parse(children);
1612     return ret;
1613   }
1614
1615   final List<SortKey> parseSortKeys(Node node)
1616     throws TransformerConfigurationException, XPathExpressionException
1617   {
1618     List<SortKey> ret = new LinkedList<SortKey>();
1619     while (node != null)
1620       {
1621         String namespaceUri = node.getNamespaceURI();
1622         if (Stylesheet.XSL_NS.equals(namespaceUri) &&
1623             Node.ELEMENT_NODE == node.getNodeType() &&
1624             "sort".equals(node.getLocalName()))
1625           {
1626             NamedNodeMap attrs = node.getAttributes();
1627             String s = getAttribute(attrs, "select");
1628             if (s == null)
1629               s = ".";
1630             Expr select = (Expr) xpath.compile(s);
1631             String l = getAttribute(attrs, "lang");
1632             TemplateNode lang = (l == null) ? null :
1633               parseAttributeValueTemplate(l, node);
1634             String dt = getAttribute(attrs, "data-type");
1635             TemplateNode dataType = (dt == null) ? null :
1636               parseAttributeValueTemplate(dt, node);
1637             String o = getAttribute(attrs, "order");
1638             TemplateNode order = (o == null) ? null :
1639               parseAttributeValueTemplate(o, node);
1640             String co = getAttribute(attrs, "case-order");
1641             TemplateNode caseOrder = (co == null) ? null :
1642               parseAttributeValueTemplate(co, node);
1643             ret.add(new SortKey(select, lang, dataType, order, caseOrder));
1644           }
1645         node = node.getNextSibling();
1646       }
1647     return ret;
1648   }
1649
1650   final List<WithParam> parseWithParams(Node node)
1651     throws TransformerConfigurationException, XPathExpressionException
1652   {
1653     List<WithParam> ret = new LinkedList<WithParam>();
1654     while (node != null)
1655       {
1656         String namespaceUri = node.getNamespaceURI();
1657         if (Stylesheet.XSL_NS.equals(namespaceUri) &&
1658             Node.ELEMENT_NODE == node.getNodeType() &&
1659             "with-param".equals(node.getLocalName()))
1660           {
1661             NamedNodeMap attrs = node.getAttributes();
1662             TemplateNode content = parse(node.getFirstChild());
1663             QName name =
1664               getQName(getRequiredAttribute(attrs, "name", node));
1665             String select = getAttribute(attrs, "select");
1666             if (select != null)
1667               {
1668                 if (content != null)
1669                   {
1670                     String msg = "parameter '" + name +
1671                       "' has both select and content";
1672                     DOMSourceLocator l = new DOMSourceLocator(node);
1673                     throw new TransformerConfigurationException(msg, l);
1674                   }
1675                 Expr expr = (Expr) xpath.compile(select);
1676                 ret.add(new WithParam(name, expr));
1677               }
1678             else
1679               ret.add(new WithParam(name, content));
1680           }
1681         node = node.getNextSibling();
1682       }
1683     return ret;
1684   }
1685
1686   /**
1687    * Created element nodes have a copy of the namespace nodes in the
1688    * stylesheet, except the XSLT namespace, extension namespaces, and
1689    * exclude-result-prefixes.
1690    */
1691   final void addNamespaceNodes(Node source, Node target, Document doc,
1692                                Collection<String> elementExcludeResultPrefixes)
1693   {
1694     NamedNodeMap attrs = source.getAttributes();
1695     if (attrs != null)
1696       {
1697         int len = attrs.getLength();
1698         for (int i = 0; i < len; i++)
1699           {
1700             Node attr = attrs.item(i);
1701             String uri = attr.getNamespaceURI();
1702             if (uri == XMLConstants.XMLNS_ATTRIBUTE_NS_URI)
1703               {
1704                 String prefix = attr.getLocalName();
1705                 if (XMLConstants.XMLNS_ATTRIBUTE.equals(prefix))
1706                   prefix = "#default";
1707                 String ns = attr.getNodeValue();
1708                 // Should the namespace be excluded?
1709                 if (XSL_NS.equals(ns) ||
1710                     extensionElementPrefixes.contains(prefix) ||
1711                     elementExcludeResultPrefixes.contains(prefix) ||
1712                     excludeResultPrefixes.contains(prefix))
1713                   continue;
1714                 // Is the namespace already defined on the target?
1715                 if (prefix == "#default")
1716                   prefix = null;
1717                 if (target.lookupNamespaceURI(prefix) != null)
1718                   continue;
1719                 attr = attr.cloneNode(true);
1720                 attr = doc.adoptNode(attr);
1721                 target.getAttributes().setNamedItemNS(attr);
1722               }
1723           }
1724       }
1725     Node parent = source.getParentNode();
1726     if (parent != null)
1727       addNamespaceNodes(parent, target, doc, elementExcludeResultPrefixes);
1728   }
1729
1730   static final String getAttribute(NamedNodeMap attrs, String name)
1731   {
1732     Node attr = attrs.getNamedItem(name);
1733     if (attr == null)
1734       return null;
1735     String ret = attr.getNodeValue();
1736     if (ret.length() == 0)
1737       return null;
1738     return ret;
1739   }
1740
1741   static final String getRequiredAttribute(NamedNodeMap attrs, String name,
1742                                            Node source)
1743     throws TransformerConfigurationException
1744   {
1745     String value = getAttribute(attrs, name);
1746     if (value == null || value.length() == 0)
1747       {
1748         String msg =
1749           name + " attribute is required on " + source.getNodeName();
1750         DOMSourceLocator l = new DOMSourceLocator(source);
1751         throw new TransformerConfigurationException(msg, l);
1752       }
1753     return value;
1754   }
1755
1756   // Handle user data changes when nodes are cloned etc
1757
1758   public void handle(short op, String key, Object data, Node src, Node dst)
1759   {
1760     dst.setUserData(key, data, this);
1761   }
1762
1763   public String toString()
1764   {
1765     CPStringBuilder b = new CPStringBuilder(getClass().getName());
1766     b.append("[templates=");
1767     b.append(templates);
1768     b.append("]");
1769     return b.toString();
1770   }
1771
1772 }
1773