0001: /*
0002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
0003: *
0004: * This file is part of Resin(R) Open Source
0005: *
0006: * Each copy or derived work must preserve the copyright notice and this
0007: * notice unmodified.
0008: *
0009: * Resin Open Source is free software; you can redistribute it and/or modify
0010: * it under the terms of the GNU General Public License as published by
0011: * the Free Software Foundation; either version 2 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * Resin Open Source is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
0017: * of NON-INFRINGEMENT. See the GNU General Public License for more
0018: * details.
0019: *
0020: * You should have received a copy of the GNU General Public License
0021: * along with Resin Open Source; if not, write to the
0022: * Free SoftwareFoundation, Inc.
0023: * 59 Temple Place, Suite 330
0024: * Boston, MA 02111-1307 USA
0025: *
0026: * @author Scott Ferguson
0027: */
0028:
0029: package com.caucho.xsl;
0030:
0031: import com.caucho.log.Log;
0032: import com.caucho.util.CharBuffer;
0033: import com.caucho.util.CharCursor;
0034: import com.caucho.util.CharScanner;
0035: import com.caucho.util.L10N;
0036: import com.caucho.util.StringCharCursor;
0037: import com.caucho.vfs.Encoding;
0038: import com.caucho.vfs.Path;
0039: import com.caucho.vfs.ReadStream;
0040: import com.caucho.xml.*;
0041:
0042: import org.w3c.dom.DOMException;
0043: import org.w3c.dom.Document;
0044: import org.w3c.dom.Element;
0045: import org.w3c.dom.Node;
0046: import org.w3c.dom.ProcessingInstruction;
0047: import org.w3c.dom.Text;
0048:
0049: import java.io.IOException;
0050: import java.util.ArrayList;
0051: import java.util.HashMap;
0052: import java.util.logging.Logger;
0053:
0054: /**
0055: * Parses a 'StyleScript' file. StyleScript is compatible with XSLT, adding
0056: * some syntactical sugar:
0057: *
0058: * <p>path << ... >> is shorthand for
0059: * <xsl:template match='path'> ... </xsl:template>
0060:
0061: * <p><{...}> is shorthand for
0062: * <xsl:value-of select='...'/>
0063: */
0064: class XslParser {
0065: static final Logger log = Log.open(XslParser.class);
0066: static final L10N L = new L10N(XslParser.class);
0067:
0068: private static final String XSLNS = Generator.XSLNS;
0069: private static final String XTPNS = Generator.XTPNS;
0070:
0071: // this is the value Axis wants
0072: static final String XMLNS = "http://www.w3.org/2000/xmlns/";
0073:
0074: static HashMap<String, String> _xslCommands;
0075: static HashMap<String, String> _xtpCommands;
0076:
0077: public boolean strictXsl;
0078: boolean rawText;
0079:
0080: int line;
0081: int textLine;
0082: ReadStream is;
0083: CharBuffer tag = new CharBuffer();
0084: CharBuffer text = new CharBuffer();
0085: QDocument xsl;
0086: private int peek = -1;
0087: private boolean seenCr;
0088: private String defaultMode;
0089: private HashMap<String, String> _namespaces;
0090: private HashMap macros = new HashMap();
0091: private boolean inTemplate;
0092:
0093: XslParser() {
0094: }
0095:
0096: /**
0097: * Parse an XSLT-lite document from the input stream, returning a Document.
0098: */
0099: Document parse(ReadStream is) throws IOException, XslParseException {
0100: this .is = is;
0101:
0102: line = 1;
0103: defaultMode = null;
0104: xsl = (QDocument) Xml.createDocument();
0105: if (is.getPath().getLastModified() > 0) {
0106: ArrayList<Path> depends = new ArrayList<Path>();
0107: depends.add(is.getPath());
0108: xsl.setProperty(xsl.DEPENDS, depends);
0109: }
0110:
0111: xsl.setRootFilename(is.getPath().getURL());
0112:
0113: _namespaces = new HashMap<String, String>();
0114:
0115: QNode top = (QNode) xsl.createDocumentFragment();
0116: top.setLocation(is.getPath().getURL(), is.getUserPath(), line,
0117: 0);
0118: rawText = false;
0119:
0120: String encoding = null;
0121: int ch = read();
0122:
0123: if (ch != 0xef) {
0124: } else if ((ch = read()) != 0xbb) {
0125: peek = 0xbb;
0126: ch = 0xef;
0127: } else if ((ch = read()) != 0xbf) {
0128: throw error(L.l("Expected 0xbf in UTF-8 header"));
0129: } else {
0130: is.setEncoding("UTF-8");
0131: ch = read();
0132: }
0133:
0134: if (ch == '<') {
0135: ch = read();
0136: if (ch == '?') {
0137: ProcessingInstruction pi = parsePi();
0138: if (pi.getNodeName().equals("xml")) {
0139: encoding = XmlUtil.getPIAttribute(
0140: pi.getNodeValue(), "encoding");
0141: if (encoding != null)
0142: is.setEncoding(encoding);
0143: } else
0144: top.appendChild(pi);
0145: ch = read();
0146: } else {
0147: peek = ch;
0148: ch = '<';
0149: }
0150: }
0151:
0152: parseNode(top, "", true, ch);
0153:
0154: QElement elt = null;
0155: for (Node node = top.getFirstChild(); node != null; node = node
0156: .getNextSibling()) {
0157: if (node.getNodeType() == Node.ELEMENT_NODE
0158: && node.getNodeName().equals("xsl:stylesheet")) {
0159: if (elt != null)
0160: throw error(L
0161: .l("xsl:stylesheet must be sole top element"));
0162: elt = (QElement) node;
0163: }
0164: }
0165: if (elt == null) {
0166: elt = (QElement) xsl.createElementNS(XSLNS,
0167: "xsl:stylesheet");
0168: elt.setAttribute("version", "1.0");
0169: elt.setLocation(is.getURL(), is.getUserPath(), 1, 0);
0170: elt.setAttribute("resin:stylescript", "true");
0171:
0172: Element out = xsl.createElementNS(XSLNS, "xsl:output");
0173: //out.setAttribute("method", "xtp");
0174: //out.setAttribute("disable-output-escaping", "true");
0175: //out.setAttribute("omit-xml-declaration", "true");
0176:
0177: elt.appendChild(out);
0178: elt.appendChild(top);
0179: }
0180:
0181: // elt.setAttribute("xsl-caucho", "true");
0182:
0183: if (encoding != null) {
0184: Element out = xsl.createElementNS(XSLNS, "xsl:output");
0185: out.setAttribute("encoding", encoding);
0186: elt.insertBefore(out, elt.getFirstChild());
0187: }
0188:
0189: xsl.appendChild(elt);
0190:
0191: /*
0192: if (dbg.canWrite()) {
0193: new XmlPrinter(dbg).printPrettyXml(xsl);
0194: }
0195: */
0196:
0197: return xsl;
0198: }
0199:
0200: /**
0201: * Parses in the middle of a node
0202: *
0203: * @param parent parsed children are attached to the parent node
0204: */
0205: private void parseNode(Node parent, String tagEnd,
0206: boolean isSpecial, int ch) throws IOException,
0207: XslParseException {
0208: boolean hasContent = false;
0209:
0210: text.clear();
0211: if (tagEnd == ">>" && (ch == '\n' || ch == '\r'))
0212: ch = read();
0213:
0214: while (ch >= 0) {
0215: switch (ch) {
0216: case '\\':
0217: hasContent = true;
0218: ch = read();
0219: if (ch == '<') {
0220: addText('<');
0221: ch = read();
0222: } else
0223: addText('\\');
0224: break;
0225:
0226: case '<':
0227: hasContent = true;
0228: ch = read();
0229:
0230: if (ch == '/') {
0231: ch = readTag(read());
0232: String tag = this .tag.toString();
0233: if (tag.equals(tagEnd)) {
0234: ch = skipWhitespace(ch);
0235: if (ch != '>')
0236: throw error(L.l("expected `{0}' at {1}",
0237: ">", badChar(ch)));
0238: addText(parent);
0239: if (tag.equals("xsl:template"))
0240: inTemplate = false;
0241: return;
0242: } else if (rawText) {
0243: addText("</" + tag + ">");
0244: ch = read();
0245: break;
0246: } else {
0247: throw error(L.l(
0248: "`</{0}>' has no matching open tag",
0249: tag));
0250: }
0251: } else if (ch == '#') {
0252: addText(parent);
0253: ch = parseScriptlet(parent);
0254: break;
0255: } else if (ch == '?') {
0256: addText(parent);
0257: ProcessingInstruction pi = parsePi();
0258: parent.appendChild(pi);
0259: ch = read();
0260: break;
0261: } else if (ch == '!') {
0262: addText(parent);
0263: ch = parseDecl(parent);
0264: break;
0265: } else if (ch == '{') {
0266: addText(parent);
0267: parseValueOf(parent);
0268: ch = read();
0269: break;
0270: }
0271:
0272: ch = readTag(ch);
0273: String tag = this .tag.toString();
0274:
0275: // treat the tag as XML when it has a known prefix or we aren't
0276: // in rawText mode
0277: if (!rawText && !tag.equals("")
0278: || tag.startsWith("xsl:")
0279: || tag.startsWith("jsp:")
0280: || tag.startsWith("xtp:")
0281: || macros.get(tag) != null) {
0282: addText(parent);
0283:
0284: parseElement(parent, tag, ch, isSpecial);
0285:
0286: ch = read();
0287: }
0288: // otherwise tread the tag as text
0289: else {
0290: addText("<");
0291: addText(tag);
0292: }
0293: break;
0294:
0295: case '>':
0296: int ch1 = read();
0297: if (ch1 == '>' && tagEnd == ">>") {
0298: if (text.length() > 0
0299: && text.charAt(text.length() - 1) == '\n')
0300: text.setLength(text.length() - 1);
0301: if (text.length() > 0
0302: && text.charAt(text.length() - 1) == '\r')
0303: text.setLength(text.length() - 1);
0304: if (!hasContent) {
0305: Element elt = xsl.createElementNS(XSLNS,
0306: "xsl:text");
0307: parent.appendChild(elt);
0308: addText(elt);
0309: } else
0310: addText(parent);
0311: return;
0312: } else {
0313: hasContent = true;
0314: addText('>');
0315: ch = ch1;
0316: }
0317: break;
0318:
0319: case '$':
0320: hasContent = true;
0321: ch = read();
0322: if (ch == '$') {
0323: addText('$');
0324: ch = read();
0325: } else if (ch >= 'a' && ch <= 'z' || ch >= 'A'
0326: && ch <= 'Z') {
0327: String name = parseName(ch);
0328: addText(parent);
0329: text.clear();
0330:
0331: ch = parseExtension(parent, name);
0332: } else if (ch == '(') {
0333: addText(parent);
0334: Element elt = xsl.createElementNS(XSLNS,
0335: "xsl:value-of");
0336: CharBuffer test = CharBuffer.allocate();
0337: lexToRparen(test);
0338: elt.setAttribute("select", test.close());
0339: parent.appendChild(elt);
0340: ch = read();
0341: } else {
0342: addText('$');
0343: ch = read();
0344: }
0345: break;
0346:
0347: case ' ':
0348: case '\t':
0349: case '\n':
0350: case '\r':
0351: addText((char) ch);
0352: ch = read();
0353: break;
0354:
0355: case '&':
0356: ch = parseEntityReference();
0357: break;
0358:
0359: default:
0360: hasContent = true;
0361: if (isSpecial) {
0362: parseSpecial(parent, ch);
0363: ch = read();
0364: } else {
0365: addText((char) ch);
0366: ch = read();
0367: }
0368: break;
0369: }
0370: }
0371:
0372: addText(parent);
0373:
0374: if (!tagEnd.equals(""))
0375: throw error(L.l("expected close of `{0}' (open at {1})",
0376: tagEnd, ((CauchoNode) parent).getLine()));
0377: }
0378:
0379: /**
0380: * Parses an element.
0381: *
0382: * @param parent the owning node of the new child
0383: * @param name the name of the element
0384: * @param ch the current character
0385: * @param isSpecial ??
0386: *
0387: * @return the new child element
0388: */
0389: private Element parseElement(Node parent, String name, int ch,
0390: boolean isSpecial) throws IOException, XslParseException {
0391: HashMap<String, String> oldNamespaces = _namespaces;
0392:
0393: QElement element = null;
0394:
0395: int p = name.indexOf(':');
0396: if (p >= 0) {
0397: String prefix = name.substring(0, p);
0398: String uri = _namespaces.get(prefix);
0399:
0400: if (uri != null)
0401: element = (QElement) xsl.createElementNS(uri, name);
0402: else if (prefix.equals("xsl"))
0403: element = (QElement) xsl.createElementNS(XSLNS, name);
0404: }
0405:
0406: try {
0407: if (element == null)
0408: element = (QElement) xsl.createElement(name);
0409: } catch (DOMException e) {
0410: throw error(e);
0411: }
0412:
0413: element.setLocation(is.getURL(), is.getUserPath(), line, 0);
0414:
0415: ch = parseAttributes(null, element, ch, false);
0416:
0417: if (name.equals("xsl:stylesheet")) {
0418: if (element.getAttribute("parsed-content").equals("false")) {
0419: rawText = true;
0420: Element child = xsl
0421: .createElementNS(XSLNS, "xsl:output");
0422: child.setAttribute("disable-output-escaping", "yes");
0423: element.appendChild(child);
0424: }
0425: }
0426:
0427: if (rawText
0428: && (name.startsWith("xsl") || name.startsWith("xtp"))
0429: && element.getAttribute("xml:space").equals(""))
0430: element.setAttribute("xml:space", "default");
0431:
0432: if (name.equals("xsl:template")) {
0433: inTemplate = true;
0434: String macroName = element.getAttribute("name");
0435: if (!macroName.equals(""))
0436: macros.put(macroName, macroName);
0437: }
0438:
0439: String oldMode = defaultMode;
0440: if (name.equals("xtp:mode")) {
0441: defaultMode = element.getAttribute("mode");
0442: } else {
0443: parent.appendChild(element);
0444: parent = element;
0445: }
0446:
0447: if (ch == '>') {
0448: parseNode(parent, name, isSpecial
0449: && name.equals("xsl:stylesheet"), read());
0450: } else if (ch == '/') {
0451: if ((ch = read()) != '>')
0452: throw error(L.l("expected `{0}' at {1}", ">",
0453: badChar(ch)));
0454: } else
0455: throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
0456:
0457: defaultMode = oldMode;
0458: _namespaces = oldNamespaces;
0459:
0460: return element;
0461: }
0462:
0463: /**
0464: * Parses an entity reference, e.g. &lt;
0465: */
0466: private int parseEntityReference() throws IOException,
0467: XslParseException {
0468: int ch = read();
0469:
0470: if (ch == '#') {
0471: int code = 0;
0472:
0473: ch = read();
0474:
0475: if (ch == 'x') {
0476: for (ch = read(); ch > 0 && ch != ';'; ch = read()) {
0477: if (ch >= '0' && ch <= '9')
0478: code = 16 * code + ch - '0';
0479: else if (ch >= 'a' && ch <= 'f')
0480: code = 16 * code + ch - 'a' + 10;
0481: else if (ch >= 'A' && ch <= 'F')
0482: code = 16 * code + ch - 'A' + 10;
0483: else
0484: break;
0485: }
0486:
0487: if (ch == ';') {
0488: addText((char) code);
0489: return read();
0490: } else {
0491: addText("&#x");
0492: addText(String.valueOf(code));
0493: return ch;
0494: }
0495: } else {
0496: for (; ch >= '0' && ch <= '9'; ch = read()) {
0497: code = 10 * code + ch - '0';
0498: }
0499: }
0500:
0501: if (ch == ';') {
0502: addText((char) code);
0503: return read();
0504: } else {
0505: addText("&#");
0506: addText(String.valueOf(code));
0507: return ch;
0508: }
0509: }
0510:
0511: CharBuffer cb = CharBuffer.allocate();
0512: for (; ch >= 'a' && ch <= 'z'; ch = read())
0513: cb.append((char) ch);
0514:
0515: if (ch != ';') {
0516: addText('&');
0517: addText(cb.close());
0518: } else if (cb.matches("lt")) {
0519: addText('<');
0520: return read();
0521: } else if (cb.matches("gt")) {
0522: addText('>');
0523: return read();
0524: } else if (cb.matches("amp")) {
0525: addText('&');
0526: return read();
0527: } else if (cb.matches("quot")) {
0528: addText('"');
0529: return read();
0530: } else if (cb.matches("apos")) {
0531: addText('\'');
0532: return read();
0533: } else {
0534: addText('&');
0535: addText(cb.close());
0536: }
0537:
0538: return ch;
0539: }
0540:
0541: /**
0542: * Parses the contents of a '<#' section.
0543: */
0544: private int parseScriptlet(Node parent) throws IOException,
0545: XslParseException {
0546: String filename = is.getUserPath();
0547: int line = this .line;
0548: int ch = read();
0549:
0550: QNode node;
0551: if (ch == '=') {
0552: node = (QNode) xsl.createElementNS(XTPNS, "xtp:expression");
0553: ch = read();
0554: } else if (ch == '!') {
0555: node = (QNode) xsl
0556: .createElementNS(XTPNS, "xtp:declaration");
0557: ch = read();
0558: } else if (ch == '@') {
0559: parseDirective(parent);
0560: return read();
0561: } else
0562: node = (QNode) xsl.createElementNS(XTPNS, "xtp:scriptlet");
0563: node.setLocation(is.getURL(), is.getUserPath(), line, 0);
0564: parent.appendChild(node);
0565:
0566: text.clear();
0567: while (ch >= 0) {
0568: if (ch == '#') {
0569: ch = read();
0570: if (ch == '>')
0571: break;
0572: else
0573: addText('#');
0574: } else {
0575: addText((char) ch);
0576: ch = read();
0577: }
0578: }
0579:
0580: node.appendChild(xsl.createTextNode(text.toString()));
0581: text.clear();
0582:
0583: return read();
0584: }
0585:
0586: /**
0587: * parses an xtp directive: <#@
0588: */
0589: private void parseDirective(Node parent) throws IOException,
0590: XslParseException {
0591: int ch;
0592:
0593: ch = skipWhitespace(read());
0594: ch = readTag(ch);
0595: String name = tag.toString();
0596: if (!name.equals("page") && !name.equals("cache"))
0597: throw error(L.l("unknown directive `{0}'", name));
0598:
0599: QElement elt = (QElement) xsl.createElementNS(XTPNS,
0600: "xtp:directive." + name);
0601: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0602: parent.appendChild(elt);
0603:
0604: ch = parseAttributes(parent, elt, ch, true);
0605:
0606: if (ch != '#')
0607: throw error(L.l("expected `{0}' at {1}", "#", badChar(ch)));
0608: if ((ch = read()) != '>')
0609: throw error(L.l("expected `{0}' at {1}", ">", badChar(ch)));
0610:
0611: if (name.equals("page")) {
0612: String contentType = elt.getAttribute("contentType");
0613: if (!contentType.equals(""))
0614: parseContentType(parent, contentType);
0615: }
0616: }
0617:
0618: private int parseStatement(Node parent, int ch) throws IOException,
0619: XslParseException {
0620: ch = skipWhitespace(ch);
0621:
0622: if (ch == '$') {
0623: ch = read();
0624:
0625: if (XmlChar.isNameStart(ch)) {
0626: String name = parseName(ch);
0627:
0628: return parseExtension(parent, name);
0629: } else if (ch == '(') {
0630: Element elt = xsl
0631: .createElementNS(XSLNS, "xsl:value-of");
0632: CharBuffer test = CharBuffer.allocate();
0633: lexToRparen(test);
0634: elt.setAttribute("select", test.close());
0635: parent.appendChild(elt);
0636: return read();
0637: } else
0638: throw error(L.l("expected statement at {0}",
0639: badChar(ch)));
0640: } else if (ch == '<') {
0641: parseBlock(parent, ch);
0642: return read();
0643: } else if (ch == ';')
0644: return read();
0645: else
0646: throw error(L.l("expected statement at {0}", badChar(ch)));
0647: }
0648:
0649: private int parseExtension(Node parent, String name)
0650: throws IOException, XslParseException {
0651: int ch = read();
0652:
0653: if (name.equals("if"))
0654: return parseIf(parent, ch);
0655:
0656: String arg = (String) _xslCommands.get(name);
0657:
0658: if (arg != null) {
0659: QElement elt = (QElement) xsl.createElementNS(XSLNS, "xsl:"
0660: + name);
0661: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0662: parent.appendChild(elt);
0663:
0664: ch = skipWhitespace(ch);
0665:
0666: if (ch == '(') {
0667: parseArgs(elt, arg);
0668:
0669: ch = skipWhitespace(read());
0670: }
0671:
0672: return parseStatement(elt, ch);
0673: }
0674:
0675: arg = (String) _xtpCommands.get(name);
0676: if (arg != null) {
0677: QElement elt = (QElement) xsl.createElement("xtp:" + name);
0678: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0679: parent.appendChild(elt);
0680:
0681: ch = skipWhitespace(ch);
0682:
0683: if (ch == '(') {
0684: parseArgs(elt, arg);
0685:
0686: ch = skipWhitespace(read());
0687: }
0688:
0689: return parseStatement(elt, ch);
0690: }
0691:
0692: ch = skipWhitespace(ch);
0693:
0694: if (ch == '=') {
0695: QElement elt = (QElement) xsl.createElement("xtp:assign");
0696: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0697: elt.setAttribute("name", name.intern());
0698: parent.appendChild(elt);
0699: ch = skipWhitespace(read());
0700:
0701: if (ch != '$')
0702: return parseStatement(elt, ch);
0703: else if ((ch = read()) != '(') {
0704: peek = ch;
0705: return parseStatement(elt, ch);
0706: } else {
0707: CharBuffer test = CharBuffer.allocate();
0708: lexToRparen(test);
0709: elt.setAttribute("select", test.close());
0710: return read();
0711: }
0712: }
0713:
0714: QElement elt = (QElement) xsl.createElement(name);
0715: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0716: parent.appendChild(elt);
0717:
0718: if (ch == '(') {
0719: parseArgs(elt, arg);
0720:
0721: ch = skipWhitespace(read());
0722: }
0723:
0724: return parseStatement(elt, ch);
0725: }
0726:
0727: private int parseIf(Node parent, int ch) throws IOException,
0728: XslParseException {
0729: QElement choose = (QElement) xsl.createElementNS(XSLNS,
0730: "xsl:choose");
0731: choose.setLocation(is.getURL(), is.getUserPath(), line, 0);
0732: parent.appendChild(choose);
0733:
0734: while (true) {
0735: lexExpect(ch, '(');
0736: CharBuffer test = CharBuffer.allocate();
0737: lexToRparen(test);
0738:
0739: QElement elt = (QElement) xsl.createElementNS(XSLNS,
0740: "xsl:when");
0741: choose.appendChild(elt);
0742: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0743: elt.setAttribute("test", test.close());
0744:
0745: ch = parseStatement(elt, skipWhitespace(read()));
0746:
0747: ch = skipWhitespace(ch);
0748: if (ch != '$')
0749: return ch;
0750:
0751: ch = read();
0752: if (!XmlChar.isNameStart(ch)) {
0753: peek = ch;
0754: return '$';
0755: }
0756:
0757: String name = parseName(ch);
0758:
0759: if (!name.equals("else"))
0760: return parseExtension(parent, name);
0761:
0762: ch = skipWhitespace(read());
0763:
0764: if (ch == '<') {
0765: elt = (QElement) xsl.createElementNS(XSLNS,
0766: "xsl:otherwise");
0767: choose.appendChild(elt);
0768: elt.setLocation(is.getURL(), is.getUserPath(), line, 0);
0769:
0770: return parseStatement(elt, skipWhitespace(ch));
0771: }
0772:
0773: name = parseName(read());
0774: if (!name.equals("if"))
0775: throw error(L.l("expected $if at `${0}'", name));
0776:
0777: ch = read();
0778: }
0779: }
0780:
0781: private String parseName(int ch) throws IOException,
0782: XslParseException {
0783: CharBuffer cb = CharBuffer.allocate();
0784:
0785: for (; XmlChar.isNameChar(ch); ch = read())
0786: cb.append((char) ch);
0787:
0788: peek = ch;
0789:
0790: return cb.close();
0791: }
0792:
0793: private void parseArgs(Element elt, String arg) throws IOException,
0794: XslParseException {
0795: CharBuffer cb = CharBuffer.allocate();
0796: String key = null;
0797: boolean isFirst = true;
0798: int ch;
0799:
0800: for (ch = read(); ch >= 0 && ch != ')'; ch = read()) {
0801: cb.append((char) ch);
0802:
0803: switch (ch) {
0804: case '(':
0805: lexToRparen(cb);
0806: cb.append(')');
0807: break;
0808:
0809: case '"':
0810: case '\'':
0811: lexString(cb, ch);
0812: break;
0813:
0814: case '=':
0815: ch = read();
0816: if (ch == '>') {
0817: cb.setLength(cb.length() - 1);
0818: key = cb.toString().trim();
0819: cb.clear();
0820: } else {
0821: peek = ch;
0822: }
0823: break;
0824:
0825: case ',':
0826: cb.setLength(cb.length() - 1);
0827: if (key != null)
0828: elt.setAttribute(key, cb.toString());
0829: else if (arg != null && isFirst)
0830: elt.setAttribute(arg, cb.toString());
0831: else
0832: throw error(L.l("unexpected arg `{0}'", cb));
0833: cb.clear();
0834: isFirst = false;
0835: key = null;
0836: break;
0837: }
0838:
0839: }
0840:
0841: if (ch != ')')
0842: throw error(L.l("expected `{0}' at {1}", ")", badChar(ch)));
0843:
0844: if (key != null)
0845: elt.setAttribute(key, cb.close());
0846: else if (arg != null && cb.length() > 0 && isFirst)
0847: elt.setAttribute(arg, cb.close());
0848: else if (cb.length() > 0)
0849: throw error(L.l("unexpected arg `{0}'", cb));
0850: }
0851:
0852: /**
0853: * Scan the buffer up to the right parenthesis.
0854: *
0855: * @param cb buffer holding the contents.
0856: */
0857: private void lexToRparen(CharBuffer cb) throws IOException,
0858: XslParseException {
0859: String filename = getFilename();
0860: int line = getLine();
0861: int ch;
0862:
0863: for (ch = read(); ch >= 0 && ch != ')'; ch = read()) {
0864: cb.append((char) ch);
0865:
0866: switch (ch) {
0867: case '(':
0868: lexToRparen(cb);
0869: cb.append(')');
0870: break;
0871:
0872: case '"':
0873: case '\'':
0874: lexString(cb, ch);
0875: break;
0876: }
0877: }
0878:
0879: if (ch != ')')
0880: throw error(L.l("expected `{0}' at {1}. Open at {2}", ")",
0881: badChar(ch), filename + ":" + line));
0882: }
0883:
0884: private void lexString(CharBuffer cb, int end) throws IOException,
0885: XslParseException {
0886: int ch;
0887:
0888: for (ch = read(); ch >= 0 && ch != end; ch = read()) {
0889: cb.append((char) ch);
0890: }
0891:
0892: if (ch != end)
0893: throw error(L.l("expected `{0}' at {1}", "" + (char) end,
0894: badChar(ch)));
0895:
0896: cb.append((char) end);
0897: }
0898:
0899: private void lexExpect(int ch, int match) throws IOException,
0900: XslParseException {
0901: for (; XmlChar.isWhitespace((char) ch); ch = read()) {
0902: }
0903:
0904: if (ch != match)
0905: throw error(L.l("expected `{0}' at {1}", "" + (char) match,
0906: badChar(ch)));
0907: }
0908:
0909: private CharScanner wsScanner = new CharScanner(" \t");
0910: private CharScanner delimScanner = new CharScanner(" \t;=");
0911:
0912: /**
0913: * parse the content-type, possibly changing character-encoding.
0914: */
0915: private void parseContentType(Node parent, String contentType)
0916: throws IOException, XslParseException {
0917: CharCursor cursor = new StringCharCursor(contentType);
0918:
0919: CharBuffer buf = new CharBuffer();
0920: wsScanner.skip(cursor);
0921: delimScanner.scan(cursor, buf);
0922:
0923: if (buf.length() <= 0)
0924: return;
0925:
0926: Element output = xsl.createElementNS(XSLNS, "xsl:output");
0927: parent.appendChild(output);
0928: output.setAttribute("media-type", buf.toString());
0929: delimScanner.skip(cursor);
0930:
0931: buf.clear();
0932: delimScanner.scan(cursor, buf);
0933: wsScanner.skip(cursor);
0934: if (cursor.current() == '=' && buf.toString().equals("charset")) {
0935: delimScanner.skip(cursor);
0936: buf.clear();
0937: delimScanner.scan(cursor, buf);
0938: if (buf.length() > 0) {
0939: output.setAttribute("encoding", Encoding
0940: .getMimeName(buf.toString()));
0941: is.setEncoding(buf.toString());
0942: }
0943: }
0944: }
0945:
0946: /**
0947: * Parses the attributes of an element.
0948: *
0949: * @param parent the elements parent.
0950: * @param elt the element itself
0951: * @param ch the next character
0952: * @param isDirective true if this is a special directive
0953: *
0954: * @return the next character
0955: */
0956: private int parseAttributes(Node parent, Element elt, int ch,
0957: boolean isDirective) throws IOException, XslParseException {
0958: HashMap<String, String> newNamespaces = null;
0959:
0960: ch = skipWhitespace(ch);
0961: while (XmlChar.isNameStart(ch)) {
0962: ch = readTag(ch);
0963: String name = tag.toString();
0964:
0965: ch = skipWhitespace(ch);
0966: String value = null;
0967: if (ch == '=') {
0968: ch = skipWhitespace(read());
0969: ch = readValue(ch);
0970: ch = skipWhitespace(ch);
0971:
0972: value = tag.toString();
0973: }
0974:
0975: int p;
0976: if (isDirective && name.equals("import")) {
0977: Element copy = (Element) elt.cloneNode(false);
0978: copy.setAttribute(name, value);
0979: parent.appendChild(copy);
0980: } else if (name.startsWith("xmlns")) {
0981: QElement qElt = (QElement) elt;
0982: if (newNamespaces == null) {
0983: newNamespaces = new HashMap<String, String>(
0984: _namespaces);
0985: _namespaces = newNamespaces;
0986: }
0987:
0988: String prefix;
0989: if (name.startsWith("xmlns:"))
0990: prefix = name.substring(6);
0991: else
0992: prefix = "";
0993:
0994: _namespaces.put(prefix, value);
0995: qElt.setAttributeNS(XMLNS, name, value);
0996:
0997: // backpatch if modify own name
0998: if (prefix != ""
0999: && qElt.getNodeName().startsWith(prefix)) {
1000: QDocument doc = (QDocument) xsl;
1001: QName newName = doc.createName(value, qElt
1002: .getNodeName());
1003: qElt.setName(newName);
1004: }
1005: } else if ((p = name.indexOf(':')) >= 0) {
1006: String prefix = name.substring(0, p);
1007: String uri = _namespaces.get(prefix);
1008:
1009: if (uri != null) {
1010: QElement qElt = (QElement) elt;
1011: qElt.setAttributeNS(uri, name, value);
1012: } else
1013: elt.setAttribute(name, value);
1014: } else {
1015: elt.setAttribute(name, value);
1016: }
1017: }
1018:
1019: return ch;
1020: }
1021:
1022: /**
1023: * Parses a processing instruction
1024: */
1025: private ProcessingInstruction parsePi() throws IOException,
1026: XslParseException {
1027: int ch = read();
1028:
1029: if (!XmlChar.isNameStart(ch))
1030: throw error(L.l("expected name at {0}", badChar(ch)));
1031:
1032: ch = readTag(ch);
1033: String name = tag.toString();
1034:
1035: text.clear();
1036: while (ch >= 0) {
1037: if (ch == '?') {
1038: if ((ch = read()) == '>') {
1039: ProcessingInstruction pi;
1040: pi = xsl.createProcessingInstruction(name, text
1041: .toString());
1042: text.clear();
1043: return pi;
1044: } else
1045: addText('?');
1046: } else {
1047: addText((char) ch);
1048: ch = read();
1049: }
1050: }
1051:
1052: throw error(L.l("expected `{0}' at {1}", ">", badChar(-1)));
1053: }
1054:
1055: private int parseDecl(Node parent) throws IOException,
1056: XslParseException {
1057: int ch = read();
1058:
1059: if (ch == '[') {
1060: if ((ch = read()) != 'C') {
1061: addText("<![");
1062: return ch;
1063: } else if ((ch = read()) != 'D') {
1064: addText("<![C");
1065: return ch;
1066: } else if ((ch = read()) != 'A') {
1067: addText("<![CD");
1068: return ch;
1069: } else if ((ch = read()) != 'T') {
1070: addText("<![CDA");
1071: return ch;
1072: } else if ((ch = read()) != 'A') {
1073: addText("<![CDAT");
1074: return ch;
1075: } else if ((ch = read()) != '[') {
1076: addText("<![CDATA");
1077: return ch;
1078: } else {
1079: ch = read();
1080:
1081: while (ch > 0) {
1082: if (ch == ']') {
1083: ch = read();
1084:
1085: while (ch == ']') {
1086: if ((ch = read()) == '>')
1087: return read();
1088: else
1089: addText(']');
1090: }
1091:
1092: addText(']');
1093: } else {
1094: addText((char) ch);
1095: ch = read();
1096: }
1097: }
1098:
1099: return ch;
1100: }
1101: }
1102:
1103: if (ch != '-') {
1104: addText("<!");
1105: return ch;
1106: }
1107:
1108: if ((ch = read()) != '-') {
1109: addText("<!-");
1110: return ch;
1111: }
1112:
1113: while (ch >= 0) {
1114: if ((ch = read()) == '-') {
1115: ch = read();
1116: while (ch == '-') {
1117: if ((ch = read()) == '>')
1118: return read();
1119: }
1120: }
1121: }
1122:
1123: throw error(L.l("expected `{0}' at {1}", "-->", badChar(-1)));
1124: }
1125:
1126: /**
1127: * Parses the shortcut for valueOf <{...}>
1128: */
1129: private void parseValueOf(Node parent) throws IOException,
1130: XslParseException {
1131: int ch = read();
1132: while (ch >= 0) {
1133: if (ch == '}') {
1134: ch = read();
1135: if (ch == '>') {
1136: QElement elt;
1137: elt = (QElement) xsl.createElementNS(XSLNS,
1138: "xsl:value-of");
1139: elt.setAttribute("select", text.toString());
1140: elt.setLocation(is.getURL(), is.getUserPath(),
1141: line, 0);
1142: parent.appendChild(elt);
1143: text.clear();
1144: return;
1145: } else
1146: addText('}');
1147: } else {
1148: addText((char) ch);
1149: ch = read();
1150: }
1151: }
1152: }
1153:
1154: /**
1155: * parses top-level templates:
1156: *
1157: * pattern << ... >> -- <xsl:template match='pattern'>...</xsl:template>
1158: * pattern <# ... #> -- <xsl:template match='pattern'>
1159: * <xtp:scriptlet>...</xtp:scriptlet>
1160: * </xsl:template>
1161: * pattern <#= ... #> -- <xsl:template match='pattern'>
1162: * <xtp:expression>...</xtp:expression>
1163: * </xsl:template>
1164: */
1165: private void parseSpecial(Node parent, int ch) throws IOException,
1166: XslParseException {
1167: char tail = '#';
1168: String element = "xtp:scriptlet";
1169:
1170: text.clear();
1171: String filename = is.getUserPath();
1172: int line = this .line;
1173: while (ch >= 0) {
1174: if (ch == '<') {
1175: filename = is.getUserPath();
1176: line = this .line;
1177: ch = read();
1178: if (ch == '#') {
1179: tail = '#';
1180: ch = read();
1181: if (ch == '=') {
1182: ch = read();
1183: element = "xtp:expression";
1184: }
1185: break;
1186: } else if (ch == '<') {
1187: tail = '>';
1188: break;
1189: } else if (ch == '\\') {
1190: addText((char) read());
1191: ch = read();
1192: }
1193: } else {
1194: addText((char) ch);
1195: ch = read();
1196: }
1197: }
1198:
1199: while (text.length() > 0
1200: && Character.isSpace(text.charAt(text.length() - 1))) {
1201: text.setLength(text.length() - 1);
1202: }
1203:
1204: QElement template = (QElement) xsl.createElementNS(XSLNS,
1205: "xsl:template");
1206: parent.appendChild(template);
1207: String match = text.toString();
1208: template.setAttribute("match", match);
1209: boolean isName = true;
1210:
1211: for (int i = 0; i < match.length(); i++) {
1212: if (!XmlChar.isNameChar(match.charAt(i))) {
1213: isName = false;
1214: break;
1215: }
1216: }
1217:
1218: if (isName && false) // XXX: problems
1219: template.setAttribute("name", match);
1220: if (defaultMode != null)
1221: template.setAttribute("mode", defaultMode);
1222: template.setLocation(filename, filename, line, 0);
1223:
1224: text.clear();
1225: inTemplate = true;
1226:
1227: if (tail == '>') {
1228: if (rawText)
1229: template.setAttribute("xml:space", "preserve");
1230: parseNode(template, ">>", false, read());
1231: inTemplate = false;
1232: return;
1233: }
1234:
1235: QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element);
1236: scriptlet.setLocation(filename, filename, line, 0);
1237:
1238: while (ch >= 0) {
1239: if (ch == tail) {
1240: ch = read();
1241: if (ch == '>')
1242: break;
1243: else
1244: addText(tail);
1245: } else {
1246: addText((char) ch);
1247: ch = read();
1248: }
1249: }
1250:
1251: scriptlet.appendChild(xsl.createTextNode(text.toString()));
1252: template.appendChild(scriptlet);
1253: text.clear();
1254: inTemplate = false;
1255: }
1256:
1257: private void parseBlock(Node parent, int ch) throws IOException,
1258: XslParseException {
1259: char tail = '#';
1260: String element = "xtp:scriptlet";
1261:
1262: for (; XmlChar.isWhitespace((char) ch); ch = read()) {
1263: }
1264:
1265: if (ch == ';')
1266: return;
1267:
1268: if (ch != '<')
1269: throw error(L.l("expected `{0}' at {1}", "<", badChar(ch)));
1270:
1271: String filename = is.getUserPath();
1272: int line = this .line;
1273: ch = read();
1274: if (ch == '#') {
1275: tail = '#';
1276: ch = read();
1277: if (ch == '=') {
1278: ch = read();
1279: element = "xtp:expression";
1280: }
1281: } else if (ch == '<') {
1282: tail = '>';
1283: } else
1284: throw error(L.l("expected block at {1}", "block",
1285: badChar(ch)));
1286:
1287: if (tail == '>') {
1288: if (rawText)
1289: ((Element) parent)
1290: .setAttribute("xml:space", "preserve");
1291: parseNode(parent, ">>", false, read());
1292: return;
1293: }
1294:
1295: QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element);
1296: scriptlet.setLocation(filename, filename, line, 0);
1297:
1298: while (ch >= 0) {
1299: if (ch == tail) {
1300: ch = read();
1301: if (ch == '>')
1302: break;
1303: else
1304: addText(tail);
1305: } else {
1306: addText((char) ch);
1307: ch = read();
1308: }
1309: }
1310:
1311: scriptlet.appendChild(xsl.createTextNode(text.toString()));
1312: parent.appendChild(scriptlet);
1313: text.clear();
1314: }
1315:
1316: private void addText(char ch) {
1317: if (text.length() == 0) {
1318: if (ch == '\n')
1319: textLine = line - 1;
1320: else
1321: textLine = line;
1322: }
1323: text.append(ch);
1324: }
1325:
1326: private void addText(String s) {
1327: if (text.length() == 0)
1328: textLine = line;
1329: text.append(s);
1330: }
1331:
1332: private int skipWhitespace(int ch) throws IOException {
1333: for (; XmlChar.isWhitespace(ch); ch = read()) {
1334: }
1335:
1336: return ch;
1337: }
1338:
1339: private int readTag(int ch) throws IOException {
1340: tag.clear();
1341: for (; XmlChar.isNameChar(ch); ch = read())
1342: tag.append((char) ch);
1343:
1344: return ch;
1345: }
1346:
1347: /**
1348: * Scans an attribute value, storing the results in <code>tag</code>.
1349: *
1350: * @param ch the current read character.
1351: * @return the next read character after the value.
1352: */
1353: private int readValue(int ch) throws IOException, XslParseException {
1354: tag.clear();
1355:
1356: if (ch == '\'') {
1357: for (ch = read(); ch >= 0 && ch != '\''; ch = read()) {
1358: if (ch == '&') {
1359: ch = parseEntityReference();
1360: tag.append(text);
1361: text.clear();
1362: unread(ch);
1363: } else
1364: tag.append((char) ch);
1365: }
1366:
1367: if (ch != '\'')
1368: throw error(L.l("expected `{0}' at {1}", "'",
1369: badChar(ch)));
1370: return read();
1371: } else if (ch == '"') {
1372: for (ch = read(); ch >= 0 && ch != '"'; ch = read()) {
1373: if (ch == '&') {
1374: ch = parseEntityReference();
1375: tag.append(text);
1376: text.clear();
1377: unread(ch);
1378: } else
1379: tag.append((char) ch);
1380: }
1381:
1382: if (ch != '\"')
1383: throw error(L.l("expected `{0}' at {1}", "\"",
1384: badChar(ch)));
1385:
1386: return read();
1387: } else if (XmlChar.isNameChar(ch)) {
1388: for (; XmlChar.isNameChar(ch); ch = read())
1389: tag.append((char) ch);
1390:
1391: return ch;
1392: } else
1393: throw error(L.l("expected attribute value at {0}",
1394: badChar(ch)));
1395: }
1396:
1397: /**
1398: * Add the current accumulated text to the parent as a text node.
1399: *
1400: * @param parent node to contain the text.
1401: */
1402: private void addText(Node parent) {
1403: if (text.getLength() == 0) {
1404: } else {
1405: Text textNode = (Text) xsl.createTextNode(text.toString());
1406: QAbstractNode node = (QAbstractNode) textNode;
1407:
1408: node
1409: .setLocation(is.getURL(), is.getUserPath(),
1410: textLine, 0);
1411: parent.appendChild(textNode);
1412: }
1413: text.clear();
1414: }
1415:
1416: /**
1417: * Returns an error including the current filename and line in emacs style.
1418: *
1419: * @param message the error message.
1420: */
1421: private XslParseException error(String message) {
1422: return new XslParseException(getFilename() + ":" + getLine()
1423: + ": " + message);
1424: }
1425:
1426: /**
1427: * Returns an error including the current filename and line in emacs style.
1428: *
1429: * @param message the error message.
1430: */
1431: private XslParseException error(Exception e) {
1432: if (e.getMessage() != null)
1433: return new XslParseException(getFilename() + ":"
1434: + getLine() + ": " + e.getMessage());
1435: else
1436: return new XslParseException(getFilename() + ":"
1437: + getLine() + ": " + e);
1438: }
1439:
1440: /**
1441: * Return the source filename.
1442: */
1443: private String getFilename() {
1444: return is.getPath().getUserPath();
1445: }
1446:
1447: /**
1448: * Return the source line.
1449: */
1450: private int getLine() {
1451: return line;
1452: }
1453:
1454: /**
1455: * Returns a string for the error character.
1456: */
1457: private String badChar(int ch) {
1458: if (ch < 0)
1459: return L.l("end of file");
1460: else if (ch == '\n' || ch == '\r')
1461: return L.l("end of line");
1462: else
1463: return "`" + (char) ch + "'";
1464: }
1465:
1466: /**
1467: * Reads a character from the stream, keeping track of newlines.
1468: */
1469: public int read() throws IOException {
1470: if (peek >= 0) {
1471: int ch = peek;
1472: peek = -1;
1473: return ch;
1474: }
1475:
1476: int ch = is.readChar();
1477: if (ch == '\r') {
1478: if ((ch = is.readChar()) != '\n') {
1479: if (ch >= 0) {
1480: if (ch == '\r')
1481: peek = '\n';
1482: else
1483: peek = ch;
1484: }
1485: }
1486: ch = '\n';
1487: }
1488:
1489: if (ch == '\n')
1490: line++;
1491:
1492: return ch;
1493: }
1494:
1495: void unread(int ch) {
1496: peek = ch;
1497: }
1498:
1499: static {
1500: _xslCommands = new HashMap<String, String>();
1501: _xslCommands.put("apply-templates", "select");
1502: _xslCommands.put("call-template", "name");
1503: _xslCommands.put("apply-imports", "");
1504: _xslCommands.put("for-each", "select");
1505: _xslCommands.put("value-of", "select");
1506: _xslCommands.put("copy-of", "select");
1507: _xslCommands.put("number", "value");
1508: _xslCommands.put("choose", "");
1509: _xslCommands.put("when", "test");
1510: _xslCommands.put("otherwise", "");
1511: _xslCommands.put("if", "test");
1512: _xslCommands.put("text", "");
1513: _xslCommands.put("copy", "");
1514: _xslCommands.put("variable", "name");
1515: _xslCommands.put("param", "name");
1516: _xslCommands.put("with-param", "name");
1517: _xslCommands.put("message", "");
1518: _xslCommands.put("fallback", "");
1519: _xslCommands.put("processing-instruction", "name");
1520: _xslCommands.put("comment", "");
1521: _xslCommands.put("element", "name");
1522: _xslCommands.put("attribute", "name");
1523: _xslCommands.put("import", "href");
1524: _xslCommands.put("include", "href");
1525: _xslCommands.put("strip-space", "elements");
1526: _xslCommands.put("preserve-space", "elements");
1527: _xslCommands.put("output", "");
1528: _xslCommands.put("key", "");
1529: _xslCommands.put("decimal-format", "");
1530: _xslCommands.put("attribute-set", "name");
1531: _xslCommands.put("variable", "name");
1532: _xslCommands.put("param", "name");
1533: _xslCommands.put("template", "match");
1534: _xslCommands.put("namespace-alias", ""); // two args
1535: // xslt 2.0
1536: _xslCommands.put("result-document", "href");
1537:
1538: _xtpCommands = new HashMap<String, String>();
1539: _xtpCommands.put("while", "test");
1540: _xtpCommands.put("expression", "expr");
1541: _xtpCommands.put("expr", "expr");
1542: _xtpCommands.put("scriptlet", "");
1543: _xtpCommands.put("declaration", "");
1544: _xtpCommands.put("directive.page", "");
1545: _xtpCommands.put("directive.cache", "");
1546: }
1547: }
|