KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > caucho > xsl > XslParser


1 /*
2  * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
3  *
4  * This file is part of Resin(R) Open Source
5  *
6  * Each copy or derived work must preserve the copyright notice and this
7  * notice unmodified.
8  *
9  * Resin Open Source is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Resin Open Source is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
17  * of NON-INFRINGEMENT. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Resin Open Source; if not, write to the
22  * Free SoftwareFoundation, Inc.
23  * 59 Temple Place, Suite 330
24  * Boston, MA 02111-1307 USA
25  *
26  * @author Scott Ferguson
27  */

28
29 package com.caucho.xsl;
30
31 import com.caucho.log.Log;
32 import com.caucho.util.CharBuffer;
33 import com.caucho.util.CharCursor;
34 import com.caucho.util.CharScanner;
35 import com.caucho.util.L10N;
36 import com.caucho.util.StringCharCursor;
37 import com.caucho.vfs.Encoding;
38 import com.caucho.vfs.Path;
39 import com.caucho.vfs.ReadStream;
40 import com.caucho.xml.*;
41
42 import org.w3c.dom.DOMException JavaDoc;
43 import org.w3c.dom.Document JavaDoc;
44 import org.w3c.dom.Element JavaDoc;
45 import org.w3c.dom.Node JavaDoc;
46 import org.w3c.dom.ProcessingInstruction JavaDoc;
47 import org.w3c.dom.Text JavaDoc;
48
49 import java.io.IOException JavaDoc;
50 import java.util.ArrayList JavaDoc;
51 import java.util.HashMap JavaDoc;
52 import java.util.logging.Logger JavaDoc;
53
54 /**
55  * Parses a 'StyleScript' file. StyleScript is compatible with XSLT, adding
56  * some syntactical sugar:
57  *
58  * <p>path &lt;&lt; ... >> is shorthand for
59  * &lt;xsl:template match='path'> ... &lt;/xsl:template>
60
61  * <p>&lt;{...}> is shorthand for
62  * &lt;xsl:value-of select='...'/>
63  */

64 class XslParser {
65   static final Logger JavaDoc log = Log.open(XslParser.class);
66   static final L10N L = new L10N(XslParser.class);
67
68   private static final String JavaDoc XSLNS = Generator.XSLNS;
69   private static final String JavaDoc XTPNS = Generator.XTPNS;
70
71   // this is the value Axis wants
72
static final String JavaDoc XMLNS = "http://www.w3.org/2000/xmlns/";
73   
74   static HashMap JavaDoc<String JavaDoc,String JavaDoc> _xslCommands;
75   static HashMap JavaDoc<String JavaDoc,String JavaDoc> _xtpCommands;
76
77   public boolean strictXsl;
78   boolean rawText;
79
80   int line;
81   int textLine;
82   ReadStream is;
83   CharBuffer tag = new CharBuffer();
84   CharBuffer text = new CharBuffer();
85   QDocument xsl;
86   private int peek = -1;
87   private boolean seenCr;
88   private String JavaDoc defaultMode;
89   private HashMap JavaDoc<String JavaDoc,String JavaDoc> _namespaces;
90   private HashMap JavaDoc macros = new HashMap JavaDoc();
91   private boolean inTemplate;
92
93   XslParser()
94   {
95   }
96
97   /**
98    * Parse an XSLT-lite document from the input stream, returning a Document.
99    */

100   Document JavaDoc parse(ReadStream is)
101     throws IOException JavaDoc, XslParseException
102   {
103     this.is = is;
104
105     line = 1;
106     defaultMode = null;
107     xsl = (QDocument) Xml.createDocument();
108     if (is.getPath().getLastModified() > 0) {
109       ArrayList JavaDoc<Path> depends = new ArrayList JavaDoc<Path>();
110       depends.add(is.getPath());
111       xsl.setProperty(xsl.DEPENDS, depends);
112     }
113
114     xsl.setRootFilename(is.getPath().getURL());
115
116     _namespaces = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
117
118     QNode top = (QNode) xsl.createDocumentFragment();
119     top.setLocation(is.getPath().getURL(), is.getUserPath(), line, 0);
120     rawText = false;
121
122     String JavaDoc encoding = null;
123     int ch = read();
124
125     if (ch != 0xef) {
126     } else if ((ch = read()) != 0xbb) {
127       peek = 0xbb;
128       ch = 0xef;
129     } else if ((ch = read()) != 0xbf) {
130       throw error(L.l("Expected 0xbf in UTF-8 header"));
131     } else {
132       is.setEncoding("UTF-8");
133       ch = read();
134     }
135     
136     if (ch == '<') {
137       ch = read();
138       if (ch == '?') {
139     ProcessingInstruction JavaDoc pi = parsePi();
140     if (pi.getNodeName().equals("xml")) {
141       encoding = XmlUtil.getPIAttribute(pi.getNodeValue(), "encoding");
142       if (encoding != null)
143         is.setEncoding(encoding);
144     }
145     else
146       top.appendChild(pi);
147     ch = read();
148       } else {
149     peek = ch;
150     ch = '<';
151       }
152     }
153     
154     parseNode(top, "", true, ch);
155
156     QElement elt = null;
157     for (Node JavaDoc node = top.getFirstChild();
158      node != null;
159      node = node.getNextSibling()) {
160       if (node.getNodeType() == Node.ELEMENT_NODE &&
161       node.getNodeName().equals("xsl:stylesheet")) {
162     if (elt != null)
163       throw error(L.l("xsl:stylesheet must be sole top element"));
164     elt = (QElement) node;
165       }
166     }
167     if (elt == null) {
168       elt = (QElement) xsl.createElementNS(XSLNS, "xsl:stylesheet");
169       elt.setAttribute("version", "1.0");
170       elt.setLocation(is.getURL(), is.getUserPath(), 1, 0);
171       elt.setAttribute("resin:stylescript", "true");
172
173       Element JavaDoc out = xsl.createElementNS(XSLNS, "xsl:output");
174       //out.setAttribute("method", "xtp");
175
//out.setAttribute("disable-output-escaping", "true");
176
//out.setAttribute("omit-xml-declaration", "true");
177

178       elt.appendChild(out);
179       elt.appendChild(top);
180     }
181
182     // elt.setAttribute("xsl-caucho", "true");
183

184     if (encoding != null) {
185       Element JavaDoc out = xsl.createElementNS(XSLNS, "xsl:output");
186       out.setAttribute("encoding", encoding);
187       elt.insertBefore(out, elt.getFirstChild());
188     }
189
190     xsl.appendChild(elt);
191
192     /*
193     if (dbg.canWrite()) {
194       new XmlPrinter(dbg).printPrettyXml(xsl);
195     }
196     */

197
198     return xsl;
199   }
200
201   /**
202    * Parses in the middle of a node
203    *
204    * @param parent parsed children are attached to the parent node
205    */

206   private void parseNode(Node JavaDoc parent, String JavaDoc tagEnd,
207              boolean isSpecial, int ch)
208     throws IOException JavaDoc, XslParseException
209   {
210     boolean hasContent = false;
211     
212     text.clear();
213     if (tagEnd == ">>" && (ch == '\n' || ch == '\r'))
214       ch = read();
215
216     while (ch >= 0) {
217       switch (ch) {
218       case '\\':
219         hasContent = true;
220     ch = read();
221     if (ch == '<') {
222       addText('<');
223       ch = read();
224     }
225     else
226       addText('\\');
227     break;
228
229       case '<':
230         hasContent = true;
231     ch = read();
232
233     if (ch == '/') {
234       ch = readTag(read());
235       String JavaDoc tag = this.tag.toString();
236       if (tag.equals(tagEnd)) {
237         ch = skipWhitespace(ch);
238         if (ch != '>')
239           throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
240         addText(parent);
241             if (tag.equals("xsl:template"))
242               inTemplate = false;
243         return;
244       }
245           else if (rawText) {
246             addText("</" + tag + ">");
247             ch = read();
248             break;
249           }
250           else {
251             throw error(L.l("`</{0}>' has no matching open tag", tag));
252           }
253     } else if (ch == '#') {
254       addText(parent);
255       ch = parseScriptlet(parent);
256       break;
257     } else if (ch == '?') {
258       addText(parent);
259       ProcessingInstruction JavaDoc pi = parsePi();
260       parent.appendChild(pi);
261       ch = read();
262       break;
263     } else if (ch == '!') {
264       addText(parent);
265       ch = parseDecl(parent);
266       break;
267     } else if (ch == '{') {
268       addText(parent);
269       parseValueOf(parent);
270       ch = read();
271       break;
272     }
273
274     ch = readTag(ch);
275     String JavaDoc tag = this.tag.toString();
276
277     // treat the tag as XML when it has a known prefix or we aren't
278
// in rawText mode
279
if (! rawText && ! tag.equals("") ||
280         tag.startsWith("xsl:") ||
281         tag.startsWith("jsp:") || tag.startsWith("xtp:") ||
282         macros.get(tag) != null) {
283       addText(parent);
284
285       parseElement(parent, tag, ch, isSpecial);
286
287       ch = read();
288     }
289     // otherwise tread the tag as text
290
else {
291       addText("<");
292       addText(tag);
293     }
294     break;
295
296       case '>':
297     int ch1 = read();
298     if (ch1 == '>' && tagEnd == ">>") {
299       if (text.length() > 0 && text.charAt(text.length() - 1) == '\n')
300         text.setLength(text.length() - 1);
301       if (text.length() > 0 && text.charAt(text.length() - 1) == '\r')
302         text.setLength(text.length() - 1);
303           if (! hasContent) {
304             Element JavaDoc elt = xsl.createElementNS(XSLNS, "xsl:text");
305             parent.appendChild(elt);
306             addText(elt);
307           }
308           else
309             addText(parent);
310       return;
311     }
312     else {
313           hasContent = true;
314       addText('>');
315       ch = ch1;
316     }
317     break;
318
319       case '$':
320         hasContent = true;
321     ch = read();
322         if (ch == '$') {
323           addText('$');
324           ch = read();
325         }
326         else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') {
327           String JavaDoc name = parseName(ch);
328           addText(parent);
329           text.clear();
330
331           ch = parseExtension(parent, name);
332         }
333         else if (ch == '(') {
334       addText(parent);
335           Element JavaDoc elt = xsl.createElementNS(XSLNS, "xsl:value-of");
336           CharBuffer test = CharBuffer.allocate();
337           lexToRparen(test);
338           elt.setAttribute("select", test.close());
339           parent.appendChild(elt);
340           ch = read();
341         }
342         else {
343           addText('$');
344           ch = read();
345         }
346         break;
347
348       case ' ': case '\t': case '\n': case '\r':
349     addText((char) ch);
350     ch = read();
351     break;
352
353       case '&':
354         ch = parseEntityReference();
355         break;
356
357       default:
358         hasContent = true;
359     if (isSpecial) {
360       parseSpecial(parent, ch);
361       ch = read();
362     }
363     else {
364       addText((char) ch);
365       ch = read();
366     }
367     break;
368       }
369     }
370
371     addText(parent);
372
373     if (! tagEnd.equals(""))
374       throw error(L.l("expected close of `{0}' (open at {1})",
375                       tagEnd, ((CauchoNode) parent).getLine()));
376   }
377
378   /**
379    * Parses an element.
380    *
381    * @param parent the owning node of the new child
382    * @param name the name of the element
383    * @param ch the current character
384    * @param isSpecial ??
385    *
386    * @return the new child element
387    */

388   private Element JavaDoc parseElement(Node JavaDoc parent, String JavaDoc name,
389                    int ch, boolean isSpecial)
390     throws IOException JavaDoc, XslParseException
391   {
392     HashMap JavaDoc<String JavaDoc,String JavaDoc> oldNamespaces = _namespaces;
393
394     QElement element = null;
395     
396     int p = name.indexOf(':');
397     if (p >= 0) {
398       String JavaDoc prefix = name.substring(0, p);
399       String JavaDoc uri = _namespaces.get(prefix);
400
401       if (uri != null)
402         element = (QElement) xsl.createElementNS(uri, name);
403       else if (prefix.equals("xsl"))
404         element = (QElement) xsl.createElementNS(XSLNS, name);
405     }
406
407     try {
408       if (element == null)
409         element = (QElement) xsl.createElement(name);
410     } catch (DOMException JavaDoc e) {
411       throw error(e);
412     }
413     
414     element.setLocation(is.getURL(), is.getUserPath(), line, 0);
415
416     ch = parseAttributes(null, element, ch, false);
417
418     if (name.equals("xsl:stylesheet")) {
419       if (element.getAttribute("parsed-content").equals("false")) {
420     rawText = true;
421         Element JavaDoc child = xsl.createElementNS(XSLNS, "xsl:output");
422         child.setAttribute("disable-output-escaping", "yes");
423         element.appendChild(child);
424       }
425     }
426
427     if (rawText && (name.startsWith("xsl") || name.startsWith("xtp")) &&
428     element.getAttribute("xml:space").equals(""))
429       element.setAttribute("xml:space", "default");
430
431     if (name.equals("xsl:template")) {
432       inTemplate = true;
433       String JavaDoc macroName = element.getAttribute("name");
434       if (! macroName.equals(""))
435     macros.put(macroName, macroName);
436     }
437
438     String JavaDoc oldMode = defaultMode;
439     if (name.equals("xtp:mode")) {
440       defaultMode = element.getAttribute("mode");
441     }
442     else {
443       parent.appendChild(element);
444       parent = element;
445     }
446
447     if (ch == '>') {
448       parseNode(parent, name, isSpecial && name.equals("xsl:stylesheet"),
449         read());
450     } else if (ch == '/') {
451       if ((ch = read()) != '>')
452     throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
453     } else
454       throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
455
456     defaultMode = oldMode;
457     _namespaces = oldNamespaces;
458
459     return element;
460   }
461
462   /**
463    * Parses an entity reference, e.g. &amp;lt;
464    */

465   private int parseEntityReference()
466     throws IOException JavaDoc, XslParseException
467   {
468     int ch = read();
469
470     if (ch == '#') {
471       int code = 0;
472
473       ch = read();
474
475       if (ch == 'x') {
476         for (ch = read(); ch > 0 && ch != ';'; ch = read()) {
477           if (ch >= '0' && ch <= '9')
478             code = 16 * code + ch - '0';
479           else if (ch >= 'a' && ch <= 'f')
480             code = 16 * code + ch - 'a' + 10;
481           else if (ch >= 'A' && ch <= 'F')
482             code = 16 * code + ch - 'A' + 10;
483           else
484             break;
485         }
486
487         if (ch == ';') {
488           addText((char) code);
489           return read();
490         }
491         else {
492           addText("&#x");
493           addText(String.valueOf(code));
494           return ch;
495         }
496       }
497       else {
498         for (; ch >= '0' && ch <= '9'; ch = read()) {
499           code = 10 * code + ch - '0';
500         }
501       }
502
503       if (ch == ';') {
504         addText((char) code);
505         return read();
506       }
507       else {
508         addText("&#");
509         addText(String.valueOf(code));
510         return ch;
511       }
512     }
513
514     CharBuffer cb = CharBuffer.allocate();
515     for (; ch >= 'a' && ch <= 'z'; ch = read())
516       cb.append((char) ch);
517
518     if (ch != ';') {
519       addText('&');
520       addText(cb.close());
521     }
522     else if (cb.matches("lt")) {
523       addText('<');
524       return read();
525     }
526     else if (cb.matches("gt")) {
527       addText('>');
528       return read();
529     }
530     else if (cb.matches("amp")) {
531       addText('&');
532       return read();
533     }
534     else if (cb.matches("quot")) {
535       addText('"');
536       return read();
537     }
538     else if (cb.matches("apos")) {
539       addText('\'');
540       return read();
541     }
542     else {
543       addText('&');
544       addText(cb.close());
545     }
546
547     return ch;
548   }
549   /**
550    * Parses the contents of a '<#' section.
551    */

552   private int parseScriptlet(Node JavaDoc parent)
553     throws IOException JavaDoc, XslParseException
554   {
555     String JavaDoc filename = is.getUserPath();
556     int line = this.line;
557     int ch = read();
558
559     QNode node;
560     if (ch == '=') {
561       node = (QNode) xsl.createElementNS(XTPNS, "xtp:expression");
562       ch = read();
563     }
564     else if (ch == '!') {
565       node = (QNode) xsl.createElementNS(XTPNS, "xtp:declaration");
566       ch = read();
567     }
568     else if (ch == '@') {
569       parseDirective(parent);
570       return read();
571     } else
572       node = (QNode) xsl.createElementNS(XTPNS, "xtp:scriptlet");
573     node.setLocation(is.getURL(), is.getUserPath(), line, 0);
574     parent.appendChild(node);
575
576     text.clear();
577     while (ch >= 0) {
578       if (ch == '#') {
579     ch = read();
580     if (ch == '>')
581       break;
582     else
583       addText('#');
584       } else {
585     addText((char) ch);
586     ch = read();
587       }
588     }
589
590     node.appendChild(xsl.createTextNode(text.toString()));
591     text.clear();
592
593     return read();
594   }
595
596   /**
597    * parses an xtp directive: <#@
598    */

599   private void parseDirective(Node JavaDoc parent)
600     throws IOException JavaDoc, XslParseException
601   {
602     int ch;
603
604     ch = skipWhitespace(read());
605     ch = readTag(ch);
606     String JavaDoc name = tag.toString();
607     if (! name.equals("page") && ! name.equals("cache"))
608       throw error(L.l("unknown directive `{0}'", name));
609
610     QElement elt = (QElement) xsl.createElementNS(XTPNS, "xtp:directive." + name);
611     elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
612     parent.appendChild(elt);
613
614     ch = parseAttributes(parent, elt, ch, true);
615
616     if (ch != '#')
617       throw error(L.l("expected `{0}' at {1}", "#", badChar(ch)));
618     if ((ch = read()) != '>')
619       throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
620
621     if (name.equals("page")) {
622       String JavaDoc contentType = elt.getAttribute("contentType");
623       if (! contentType.equals(""))
624     parseContentType(parent, contentType);
625     }
626   }
627   
628   private int parseStatement(Node JavaDoc parent, int ch)
629     throws IOException JavaDoc, XslParseException
630   {
631     ch = skipWhitespace(ch);
632
633     if (ch == '$') {
634       ch = read();
635       
636       if (XmlChar.isNameStart(ch)) {
637         String JavaDoc name = parseName(ch);
638
639         return parseExtension(parent, name);
640       }
641       else if (ch == '(') {
642         Element JavaDoc elt = xsl.createElementNS(XSLNS, "xsl:value-of");
643         CharBuffer test = CharBuffer.allocate();
644         lexToRparen(test);
645         elt.setAttribute("select", test.close());
646         parent.appendChild(elt);
647         return read();
648       }
649       else
650         throw error(L.l("expected statement at {0}", badChar(ch)));
651     }
652     else if (ch == '<') {
653       parseBlock(parent, ch);
654       return read();
655     }
656     else if (ch == ';')
657       return read();
658     else
659       throw error(L.l("expected statement at {0}", badChar(ch)));
660   }
661
662   private int parseExtension(Node JavaDoc parent, String JavaDoc name)
663     throws IOException JavaDoc, XslParseException
664   {
665     int ch = read();
666
667     if (name.equals("if"))
668       return parseIf(parent, ch);
669
670     String JavaDoc arg = (String JavaDoc) _xslCommands.get(name);
671
672     if (arg != null) {
673       QElement elt = (QElement) xsl.createElementNS(XSLNS, "xsl:" + name);
674       elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
675       parent.appendChild(elt);
676
677       ch = skipWhitespace(ch);
678
679       if (ch == '(') {
680         parseArgs(elt, arg);
681
682         ch = skipWhitespace(read());
683       }
684       
685       return parseStatement(elt, ch);
686     }
687
688     arg = (String JavaDoc) _xtpCommands.get(name);
689     if (arg != null) {
690       QElement elt = (QElement) xsl.createElement("xtp:" + name);
691       elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
692       parent.appendChild(elt);
693
694       ch = skipWhitespace(ch);
695
696       if (ch == '(') {
697         parseArgs(elt, arg);
698
699         ch = skipWhitespace(read());
700       }
701       
702       return parseStatement(elt, ch);
703     }
704     
705     ch = skipWhitespace(ch);
706       
707     if (ch == '=') {
708       QElement elt = (QElement) xsl.createElement("xtp:assign");
709       elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
710       elt.setAttribute("name", name.intern());
711       parent.appendChild(elt);
712       ch = skipWhitespace(read());
713
714       if (ch != '$')
715         return parseStatement(elt, ch);
716       else if ((ch = read()) != '(') {
717         peek = ch;
718         return parseStatement(elt, ch);
719       }
720       else {
721         CharBuffer test = CharBuffer.allocate();
722         lexToRparen(test);
723         elt.setAttribute("select", test.close());
724         return read();
725       }
726     }
727
728     QElement elt = (QElement) xsl.createElement(name);
729     elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
730     parent.appendChild(elt);
731
732     if (ch == '(') {
733       parseArgs(elt, arg);
734
735       ch = skipWhitespace(read());
736     }
737       
738     return parseStatement(elt, ch);
739   }
740
741   private int parseIf(Node JavaDoc parent, int ch)
742     throws IOException JavaDoc, XslParseException
743   {
744     QElement choose = (QElement) xsl.createElementNS(XSLNS, "xsl:choose");
745     choose.setLocation(is.getURL(), is.getUserPath(), line, 0);
746     parent.appendChild(choose);
747
748     while (true) {
749       lexExpect(ch, '(');
750       CharBuffer test = CharBuffer.allocate();
751       lexToRparen(test);
752
753       QElement elt = (QElement) xsl.createElementNS(XSLNS, "xsl:when");
754       choose.appendChild(elt);
755       elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
756       elt.setAttribute("test", test.close());
757
758       ch = parseStatement(elt, skipWhitespace(read()));
759
760       ch = skipWhitespace(ch);
761       if (ch != '$')
762         return ch;
763
764       ch = read();
765       if (! XmlChar.isNameStart(ch)) {
766         peek = ch;
767         return '$';
768       }
769       
770       String JavaDoc name = parseName(ch);
771
772       if (! name.equals("else"))
773         return parseExtension(parent, name);
774       
775       ch = skipWhitespace(read());
776
777       if (ch == '<') {
778         elt = (QElement) xsl.createElementNS(XSLNS, "xsl:otherwise");
779         choose.appendChild(elt);
780         elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
781         
782         return parseStatement(elt, skipWhitespace(ch));
783       }
784
785       name = parseName(read());
786       if (! name.equals("if"))
787         throw error(L.l("expected $if at `${0}'", name));
788
789       ch = read();
790     }
791   }
792
793   private String JavaDoc parseName(int ch)
794     throws IOException JavaDoc, XslParseException
795   {
796     CharBuffer cb = CharBuffer.allocate();
797
798     for (; XmlChar.isNameChar(ch); ch = read())
799       cb.append((char) ch);
800
801     peek = ch;
802
803     return cb.close();
804   }
805
806   private void parseArgs(Element JavaDoc elt, String JavaDoc arg)
807     throws IOException JavaDoc, XslParseException
808   {
809     CharBuffer cb = CharBuffer.allocate();
810     String JavaDoc key = null;
811     boolean isFirst = true;
812     int ch;
813     
814     for (ch = read(); ch >= 0 && ch != ')'; ch = read()) {
815       cb.append((char) ch);
816       
817       switch (ch) {
818       case '(':
819         lexToRparen(cb);
820         cb.append(')');
821         break;
822
823       case '"': case '\'':
824         lexString(cb, ch);
825         break;
826         
827       case '=':
828         ch = read();
829         if (ch == '>') {
830           cb.setLength(cb.length() - 1);
831           key = cb.toString().trim();
832           cb.clear();
833         }
834         else {
835           peek = ch;
836         }
837         break;
838         
839       case ',':
840         cb.setLength(cb.length() - 1);
841         if (key != null)
842           elt.setAttribute(key, cb.toString());
843         else if (arg != null && isFirst)
844           elt.setAttribute(arg, cb.toString());
845         else
846           throw error(L.l("unexpected arg `{0}'", cb));
847         cb.clear();
848         isFirst = false;
849         key = null;
850         break;
851       }
852
853     }
854
855     if (ch != ')')
856       throw error(L.l("expected `{0}' at {1}", ")", badChar(ch)));
857
858     if (key != null)
859       elt.setAttribute(key, cb.close());
860     else if (arg != null && cb.length() > 0 && isFirst)
861       elt.setAttribute(arg, cb.close());
862     else if (cb.length() > 0)
863       throw error(L.l("unexpected arg `{0}'", cb));
864   }
865
866   /**
867    * Scan the buffer up to the right parenthesis.
868    *
869    * @param cb buffer holding the contents.
870    */

871   private void lexToRparen(CharBuffer cb)
872     throws IOException JavaDoc, XslParseException
873   {
874     String JavaDoc filename = getFilename();
875     int line = getLine();
876     int ch;
877     
878     for (ch = read(); ch >= 0 && ch != ')'; ch = read()) {
879       cb.append((char) ch);
880       
881       switch (ch) {
882       case '(':
883         lexToRparen(cb);
884         cb.append(')');
885         break;
886
887       case '"': case '\'':
888         lexString(cb, ch);
889         break;
890       }
891     }
892
893     if (ch != ')')
894       throw error(L.l("expected `{0}' at {1}. Open at {2}",
895                       ")", badChar(ch), filename + ":" + line));
896   }
897
898   private void lexString(CharBuffer cb, int end)
899     throws IOException JavaDoc, XslParseException
900   {
901     int ch;
902     
903     for (ch = read(); ch >= 0 && ch != end; ch = read()) {
904       cb.append((char) ch);
905     }
906
907     if (ch != end)
908       throw error(L.l("expected `{0}' at {1}", "" + (char) end, badChar(ch)));
909
910     cb.append((char) end);
911   }
912
913   private void lexExpect(int ch, int match)
914     throws IOException JavaDoc, XslParseException
915   {
916     for (; XmlChar.isWhitespace((char) ch); ch = read()) {
917     }
918
919     if (ch != match)
920       throw error(L.l("expected `{0}' at {1}",
921                       "" + (char) match, badChar(ch)));
922   }
923
924   private CharScanner wsScanner = new CharScanner(" \t");
925   private CharScanner delimScanner = new CharScanner(" \t;=");
926
927   /**
928    * parse the content-type, possibly changing character-encoding.
929    */

930   private void parseContentType(Node JavaDoc parent, String JavaDoc contentType)
931     throws IOException JavaDoc, XslParseException
932   {
933     CharCursor cursor = new StringCharCursor(contentType);
934
935     CharBuffer buf = new CharBuffer();
936     wsScanner.skip(cursor);
937     delimScanner.scan(cursor, buf);
938
939     if (buf.length() <= 0)
940       return;
941
942     Element JavaDoc output = xsl.createElementNS(XSLNS, "xsl:output");
943     parent.appendChild(output);
944     output.setAttribute("media-type", buf.toString());
945     delimScanner.skip(cursor);
946
947     buf.clear();
948     delimScanner.scan(cursor, buf);
949     wsScanner.skip(cursor);
950     if (cursor.current() == '=' && buf.toString().equals("charset")) {
951       delimScanner.skip(cursor);
952       buf.clear();
953       delimScanner.scan(cursor, buf);
954       if (buf.length() > 0) {
955     output.setAttribute("encoding", Encoding.getMimeName(buf.toString()));
956     is.setEncoding(buf.toString());
957       }
958     }
959   }
960
961   /**
962    * Parses the attributes of an element.
963    *
964    * @param parent the elements parent.
965    * @param elt the element itself
966    * @param ch the next character
967    * @param isDirective true if this is a special directive
968    *
969    * @return the next character
970    */

971   private int parseAttributes(Node JavaDoc parent, Element JavaDoc elt,
972                               int ch, boolean isDirective)
973     throws IOException JavaDoc, XslParseException
974   {
975     HashMap JavaDoc<String JavaDoc,String JavaDoc> newNamespaces = null;
976     
977     ch = skipWhitespace(ch);
978     while (XmlChar.isNameStart(ch)) {
979       ch = readTag(ch);
980       String JavaDoc name = tag.toString();
981
982       ch = skipWhitespace(ch);
983       String JavaDoc value = null;
984       if (ch == '=') {
985     ch = skipWhitespace(read());
986     ch = readValue(ch);
987     ch = skipWhitespace(ch);
988
989         value = tag.toString();
990       }
991
992       int p;
993       if (isDirective && name.equals("import")) {
994         Element JavaDoc copy = (Element JavaDoc) elt.cloneNode(false);
995     copy.setAttribute(name, value);
996         parent.appendChild(copy);
997       }
998       else if (name.startsWith("xmlns")) {
999         QElement qElt = (QElement) elt;
1000        if (newNamespaces == null) {
1001          newNamespaces = new HashMap JavaDoc<String JavaDoc,String JavaDoc>(_namespaces);
1002          _namespaces = newNamespaces;
1003        }
1004
1005        String JavaDoc prefix;
1006        if (name.startsWith("xmlns:"))
1007          prefix = name.substring(6);
1008        else
1009          prefix = "";
1010
1011        _namespaces.put(prefix, value);
1012        qElt.setAttributeNS(XMLNS, name, value);
1013
1014        // backpatch if modify own name
1015
if (prefix != "" && qElt.getNodeName().startsWith(prefix)) {
1016          QDocument doc = (QDocument) xsl;
1017          QName newName = doc.createName(value, qElt.getNodeName());
1018          qElt.setName(newName);
1019        }
1020      }
1021      else if ((p = name.indexOf(':')) >= 0) {
1022        String JavaDoc prefix = name.substring(0, p);
1023        String JavaDoc uri = _namespaces.get(prefix);
1024
1025        if (uri != null) {
1026          QElement qElt = (QElement) elt;
1027          qElt.setAttributeNS(uri, name, value);
1028        }
1029        else
1030          elt.setAttribute(name, value);
1031      }
1032      else {
1033    elt.setAttribute(name, value);
1034      }
1035    }
1036
1037    return ch;
1038  }
1039
1040  /**
1041   * Parses a processing instruction
1042   */

1043  private ProcessingInstruction JavaDoc parsePi()
1044    throws IOException JavaDoc, XslParseException
1045  {
1046    int ch = read();
1047
1048    if (! XmlChar.isNameStart(ch))
1049      throw error(L.l("expected name at {0}", badChar(ch)));
1050
1051    ch = readTag(ch);
1052    String JavaDoc name = tag.toString();
1053
1054    text.clear();
1055    while (ch >= 0) {
1056      if (ch == '?') {
1057    if ((ch = read()) == '>') {
1058      ProcessingInstruction JavaDoc pi;
1059      pi = xsl.createProcessingInstruction(name, text.toString());
1060      text.clear();
1061      return pi;
1062    }
1063    else
1064      addText('?');
1065      } else {
1066    addText((char) ch);
1067    ch = read();
1068      }
1069    }
1070
1071    throw error(L.l("expected `{0}' at {1}", ">", badChar(-1)));
1072  }
1073
1074  private int parseDecl(Node JavaDoc parent)
1075    throws IOException JavaDoc, XslParseException
1076  {
1077    int ch = read();
1078
1079    if (ch == '[') {
1080      if ((ch = read()) != 'C') {
1081        addText("<![");
1082        return ch;
1083      } else if ((ch = read()) != 'D') {
1084        addText("<![C");
1085        return ch;
1086      } else if ((ch = read()) != 'A') {
1087        addText("<![CD");
1088        return ch;
1089      } else if ((ch = read()) != 'T') {
1090        addText("<![CDA");
1091        return ch;
1092      } else if ((ch = read()) != 'A') {
1093        addText("<![CDAT");
1094        return ch;
1095      } else if ((ch = read()) != '[') {
1096        addText("<![CDATA");
1097        return ch;
1098      } else {
1099        ch = read();
1100        
1101        while (ch > 0) {
1102          if (ch == ']') {
1103            ch = read();
1104
1105            while (ch == ']') {
1106              if ((ch = read()) == '>')
1107                return read();
1108              else
1109                addText(']');
1110            }
1111
1112            addText(']');
1113          }
1114          else {
1115            addText((char) ch);
1116            ch = read();
1117          }
1118        }
1119
1120        return ch;
1121      }
1122    }
1123
1124    if (ch != '-') {
1125      addText("<!");
1126      return ch;
1127    }
1128
1129    if ((ch = read()) != '-') {
1130      addText("<!-");
1131      return ch;
1132    }
1133
1134    while (ch >= 0) {
1135      if ((ch = read()) == '-') {
1136    ch = read();
1137    while (ch == '-') {
1138      if ((ch = read()) == '>')
1139        return read();
1140    }
1141      }
1142    }
1143
1144    throw error(L.l("expected `{0}' at {1}", "-->", badChar(-1)));
1145  }
1146
1147  /**
1148   * Parses the shortcut for valueOf <{...}>
1149   */

1150  private void parseValueOf(Node JavaDoc parent)
1151    throws IOException JavaDoc, XslParseException
1152  {
1153    int ch = read();
1154    while (ch >= 0) {
1155      if (ch == '}') {
1156    ch = read();
1157    if (ch == '>') {
1158      QElement elt;
1159      elt = (QElement) xsl.createElementNS(XSLNS, "xsl:value-of");
1160      elt.setAttribute("select", text.toString());
1161      elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
1162      parent.appendChild(elt);
1163      text.clear();
1164      return;
1165    }
1166    else
1167      addText('}');
1168      }
1169      else {
1170    addText((char) ch);
1171    ch = read();
1172      }
1173    }
1174  }
1175
1176  /**
1177   * parses top-level templates:
1178   *
1179   * pattern << ... >> -- <xsl:template match='pattern'>...</xsl:template>
1180   * pattern <# ... #> -- <xsl:template match='pattern'>
1181   * <xtp:scriptlet>...</xtp:scriptlet>
1182   * </xsl:template>
1183   * pattern <#= ... #> -- <xsl:template match='pattern'>
1184   * <xtp:expression>...</xtp:expression>
1185   * </xsl:template>
1186   */

1187  private void parseSpecial(Node JavaDoc parent, int ch)
1188    throws IOException JavaDoc, XslParseException
1189  {
1190    char tail = '#';
1191    String JavaDoc element = "xtp:scriptlet";
1192
1193    text.clear();
1194    String JavaDoc filename = is.getUserPath();
1195    int line = this.line;
1196    while (ch >= 0) {
1197      if (ch == '<') {
1198    filename = is.getUserPath();
1199    line = this.line;
1200    ch = read();
1201    if (ch == '#') {
1202      tail = '#';
1203      ch = read();
1204      if (ch == '=') {
1205        ch = read();
1206        element = "xtp:expression";
1207      }
1208      break;
1209    }
1210    else if (ch == '<') {
1211      tail = '>';
1212      break;
1213    }
1214    else if (ch == '\\') {
1215      addText((char) read());
1216      ch = read();
1217    }
1218      } else {
1219    addText((char) ch);
1220    ch = read();
1221      }
1222    }
1223
1224    while (text.length() > 0 &&
1225           Character.isSpace(text.charAt(text.length() - 1))) {
1226      text.setLength(text.length() - 1);
1227    }
1228    
1229    QElement template = (QElement) xsl.createElementNS(XSLNS, "xsl:template");
1230    parent.appendChild(template);
1231    String JavaDoc match = text.toString();
1232    template.setAttribute("match", match);
1233    boolean isName = true;
1234
1235    for (int i = 0; i < match.length(); i++) {
1236      if (! XmlChar.isNameChar(match.charAt(i))) {
1237        isName = false;
1238        break;
1239      }
1240    }
1241
1242    if (isName && false) // XXX: problems
1243
template.setAttribute("name", match);
1244    if (defaultMode != null)
1245      template.setAttribute("mode", defaultMode);
1246    template.setLocation(filename, filename, line, 0);
1247
1248    text.clear();
1249    inTemplate = true;
1250
1251    if (tail == '>') {
1252      if (rawText)
1253    template.setAttribute("xml:space", "preserve");
1254      parseNode(template, ">>", false, read());
1255      inTemplate = false;
1256      return;
1257    }
1258
1259    QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element);
1260    scriptlet.setLocation(filename, filename, line, 0);
1261
1262    while (ch >= 0) {
1263      if (ch == tail) {
1264    ch = read();
1265    if (ch == '>')
1266      break;
1267    else
1268      addText(tail);
1269      } else {
1270    addText((char) ch);
1271    ch = read();
1272      }
1273    }
1274
1275    scriptlet.appendChild(xsl.createTextNode(text.toString()));
1276    template.appendChild(scriptlet);
1277    text.clear();
1278    inTemplate = false;
1279  }
1280  
1281  private void parseBlock(Node JavaDoc parent, int ch)
1282    throws IOException JavaDoc, XslParseException
1283  {
1284    char tail = '#';
1285    String JavaDoc element = "xtp:scriptlet";
1286
1287    for (; XmlChar.isWhitespace((char) ch); ch = read()) {
1288    }
1289
1290    if (ch == ';')
1291      return;
1292
1293    if (ch != '<')
1294      throw error(L.l("expected `{0}' at {1}", "<", badChar(ch)));
1295    
1296    String JavaDoc filename = is.getUserPath();
1297    int line = this.line;
1298    ch = read();
1299    if (ch == '#') {
1300      tail = '#';
1301      ch = read();
1302      if (ch == '=') {
1303        ch = read();
1304        element = "xtp:expression";
1305      }
1306    }
1307    else if (ch == '<') {
1308      tail = '>';
1309    }
1310    else
1311      throw error(L.l("expected block at {1}", "block", badChar(ch)));
1312    
1313    if (tail == '>') {
1314      if (rawText)
1315    ((Element JavaDoc) parent).setAttribute("xml:space", "preserve");
1316      parseNode(parent, ">>", false, read());
1317      return;
1318    }
1319
1320    QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element);
1321    scriptlet.setLocation(filename, filename, line, 0);
1322
1323    while (ch >= 0) {
1324      if (ch == tail) {
1325    ch = read();
1326    if (ch == '>')
1327      break;
1328    else
1329      addText(tail);
1330      } else {
1331    addText((char) ch);
1332    ch = read();
1333      }
1334    }
1335
1336    scriptlet.appendChild(xsl.createTextNode(text.toString()));
1337    parent.appendChild(scriptlet);
1338    text.clear();
1339  }
1340
1341  private void addText(char ch)
1342  {
1343    if (text.length() == 0) {
1344      if (ch == '\n')
1345        textLine = line - 1;
1346      else
1347        textLine = line;
1348    }
1349    text.append(ch);
1350  }
1351
1352  private void addText(String JavaDoc s)
1353  {
1354    if (text.length() == 0)
1355      textLine = line;
1356    text.append(s);
1357  }
1358
1359  private int skipWhitespace(int ch) throws IOException JavaDoc
1360  {
1361    for (; XmlChar.isWhitespace(ch); ch = read()) {
1362    }
1363
1364    return ch;
1365  }
1366
1367  private int readTag(int ch) throws IOException JavaDoc
1368  {
1369    tag.clear();
1370    for (; XmlChar.isNameChar(ch); ch = read())
1371      tag.append((char) ch);
1372
1373    return ch;
1374  }
1375
1376  /**
1377   * Scans an attribute value, storing the results in <code>tag</code>.
1378   *
1379   * @param ch the current read character.
1380   * @return the next read character after the value.
1381   */

1382  private int readValue(int ch) throws IOException JavaDoc, XslParseException
1383  {
1384    tag.clear();
1385
1386    if (ch == '\'') {
1387      for (ch = read(); ch >= 0 && ch != '\''; ch = read()) {
1388        if (ch == '&') {
1389          ch = parseEntityReference();
1390          tag.append(text);
1391          text.clear();
1392          unread(ch);
1393        }
1394        else
1395          tag.append((char) ch);
1396      }
1397
1398      if (ch != '\'')
1399    throw error(L.l("expected `{0}' at {1}", "'", badChar(ch)));
1400      return read();
1401    } else if (ch == '"') {
1402      for (ch = read(); ch >= 0 && ch != '"'; ch = read()) {
1403        if (ch == '&') {
1404          ch = parseEntityReference();
1405          tag.append(text);
1406          text.clear();
1407          unread(ch);
1408        }
1409        else
1410          tag.append((char) ch);
1411      }
1412
1413      if (ch != '\"')
1414    throw error(L.l("expected `{0}' at {1}", "\"", badChar(ch)));
1415
1416      return read();
1417    } else if (XmlChar.isNameChar(ch)) {
1418      for (; XmlChar.isNameChar(ch); ch = read())
1419    tag.append((char) ch);
1420
1421      return ch;
1422    } else
1423      throw error(L.l("expected attribute value at {0}", badChar(ch)));
1424  }
1425
1426  /**
1427   * Add the current accumulated text to the parent as a text node.
1428   *
1429   * @param parent node to contain the text.
1430   */

1431  private void addText(Node JavaDoc parent)
1432  {
1433    if (text.getLength() == 0) {
1434    }
1435    else {
1436      Text JavaDoc textNode = (Text JavaDoc) xsl.createTextNode(text.toString());
1437      QAbstractNode node = (QAbstractNode) textNode;
1438
1439      node.setLocation(is.getURL(), is.getUserPath(), textLine, 0);
1440      parent.appendChild(textNode);
1441    }
1442    text.clear();
1443  }
1444
1445  /**
1446   * Returns an error including the current filename and line in emacs style.
1447   *
1448   * @param message the error message.
1449   */

1450  private XslParseException error(String JavaDoc message)
1451  {
1452    return new XslParseException(getFilename() + ":" + getLine() + ": " +
1453                                 message);
1454  }
1455
1456  /**
1457   * Returns an error including the current filename and line in emacs style.
1458   *
1459   * @param message the error message.
1460   */

1461  private XslParseException error(Exception JavaDoc e)
1462  {
1463    if (e.getMessage() != null)
1464      return new XslParseException(getFilename() + ":" + getLine() + ": " +
1465                                   e.getMessage());
1466    else
1467      return new XslParseException(getFilename() + ":" + getLine() + ": " +
1468                                   e);
1469  }
1470
1471  /**
1472   * Return the source filename.
1473   */

1474  private String JavaDoc getFilename()
1475  {
1476    return is.getPath().getUserPath();
1477  }
1478
1479  /**
1480   * Return the source line.
1481   */

1482  private int getLine()
1483  {
1484    return line;
1485  }
1486
1487  /**
1488   * Returns a string for the error character.
1489   */

1490  private String JavaDoc badChar(int ch)
1491  {
1492    if (ch < 0)
1493      return L.l("end of file");
1494    else if (ch == '\n' || ch == '\r')
1495      return L.l("end of line");
1496    else
1497      return "`" + (char) ch + "'";
1498  }
1499
1500  /**
1501   * Reads a character from the stream, keeping track of newlines.
1502   */

1503  public int read() throws IOException JavaDoc
1504  {
1505    if (peek >= 0) {
1506      int ch = peek;
1507      peek = -1;
1508      return ch;
1509    }
1510
1511    int ch = is.readChar();
1512    if (ch == '\r') {
1513      if ((ch = is.readChar()) != '\n') {
1514    if (ch >= 0) {
1515          if (ch == '\r')
1516            peek = '\n';
1517          else
1518            peek = ch;
1519        }
1520      }
1521      ch = '\n';
1522    }
1523      
1524    if (ch == '\n')
1525      line++;
1526
1527    return ch;
1528  }
1529
1530  void unread(int ch)
1531  {
1532    peek = ch;
1533  }
1534
1535  static {
1536    _xslCommands = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
1537    _xslCommands.put("apply-templates", "select");
1538    _xslCommands.put("call-template", "name");
1539    _xslCommands.put("apply-imports", "");
1540    _xslCommands.put("for-each", "select");
1541    _xslCommands.put("value-of", "select");
1542    _xslCommands.put("copy-of", "select");
1543    _xslCommands.put("number", "value");
1544    _xslCommands.put("choose", "");
1545    _xslCommands.put("when", "test");
1546    _xslCommands.put("otherwise", "");
1547    _xslCommands.put("if", "test");
1548    _xslCommands.put("text", "");
1549    _xslCommands.put("copy", "");
1550    _xslCommands.put("variable", "name");
1551    _xslCommands.put("param", "name");
1552    _xslCommands.put("with-param", "name");
1553    _xslCommands.put("message", "");
1554    _xslCommands.put("fallback", "");
1555    _xslCommands.put("processing-instruction", "name");
1556    _xslCommands.put("comment", "");
1557    _xslCommands.put("element", "name");
1558    _xslCommands.put("attribute", "name");
1559    _xslCommands.put("import", "href");
1560    _xslCommands.put("include", "href");
1561    _xslCommands.put("strip-space", "elements");
1562    _xslCommands.put("preserve-space", "elements");
1563    _xslCommands.put("output", "");
1564    _xslCommands.put("key", "");
1565    _xslCommands.put("decimal-format", "");
1566    _xslCommands.put("attribute-set", "name");
1567    _xslCommands.put("variable", "name");
1568    _xslCommands.put("param", "name");
1569    _xslCommands.put("template", "match");
1570    _xslCommands.put("namespace-alias", ""); // two args
1571
// xslt 2.0
1572
_xslCommands.put("result-document", "href");
1573    
1574    _xtpCommands = new HashMap JavaDoc<String JavaDoc,String JavaDoc>();
1575    _xtpCommands.put("while", "test");
1576    _xtpCommands.put("expression", "expr");
1577    _xtpCommands.put("expr", "expr");
1578    _xtpCommands.put("scriptlet", "");
1579    _xtpCommands.put("declaration", "");
1580    _xtpCommands.put("directive.page", "");
1581    _xtpCommands.put("directive.cache", "");
1582  }
1583}
1584
Popular Tags