/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * Free SoftwareFoundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.xsl; import com.caucho.util.CharBuffer; import com.caucho.util.CharCursor; import com.caucho.util.CharScanner; import com.caucho.util.L10N; import com.caucho.util.StringCharCursor; import com.caucho.vfs.Encoding; import com.caucho.vfs.Path; import com.caucho.vfs.ReadStream; import com.caucho.xml.*; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.logging.Logger; /** * Parses a 'StyleScript' file. StyleScript is compatible with XSLT, adding * some syntactical sugar: * * <p>path << ... >> is shorthand for * <xsl:template match='path'> ... </xsl:template> * <p><{...}> is shorthand for * <xsl:value-of select='...'/> */ class XslParser { private static final Logger log = Logger.getLogger(XslParser.class.getName()); static final L10N L = new L10N(XslParser.class); private static final String XSLNS = Generator.XSLNS; private static final String XTPNS = Generator.XTPNS; // this is the value Axis wants static final String XMLNS = "http://www.w3.org/2000/xmlns/"; static HashMap<String,String> _xslCommands; static HashMap<String,String> _xtpCommands; public boolean strictXsl; boolean rawText; int line; int textLine; ReadStream is; CharBuffer tag = new CharBuffer(); CharBuffer text = new CharBuffer(); QDocument xsl; private int peek = -1; private boolean seenCr; private String defaultMode; private HashMap<String,String> _namespaces; private HashMap macros = new HashMap(); private boolean inTemplate; XslParser() { } /** * Parse an XSLT-lite document from the input stream, returning a Document. */ Document parse(ReadStream is) throws IOException, XslParseException { this.is = is; line = 1; defaultMode = null; xsl = (QDocument) Xml.createDocument(); if (is.getPath().getLastModified() > 0) { ArrayList<Path> depends = new ArrayList<Path>(); depends.add(is.getPath()); xsl.setProperty(xsl.DEPENDS, depends); } xsl.setRootFilename(is.getPath().getURL()); _namespaces = new HashMap<String,String>(); QNode top = (QNode) xsl.createDocumentFragment(); top.setLocation(is.getPath().getURL(), is.getUserPath(), line, 0); rawText = false; String encoding = null; int ch = read(); if (ch != 0xef) { } else if ((ch = read()) != 0xbb) { peek = 0xbb; ch = 0xef; } else if ((ch = read()) != 0xbf) { throw error(L.l("Expected 0xbf in UTF-8 header")); } else { is.setEncoding("UTF-8"); ch = read(); } if (ch == '<') { ch = read(); if (ch == '?') { ProcessingInstruction pi = parsePi(); if (pi.getNodeName().equals("xml")) { encoding = XmlUtil.getPIAttribute(pi.getNodeValue(), "encoding"); if (encoding != null) is.setEncoding(encoding); } else top.appendChild(pi); ch = read(); } else { peek = ch; ch = '<'; } } parseNode(top, "", true, ch); QElement elt = null; for (Node node = top.getFirstChild(); node != null; node = node.getNextSibling()) { if (node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals("xsl:stylesheet")) { if (elt != null) throw error(L.l("xsl:stylesheet must be sole top element")); elt = (QElement) node; } } if (elt == null) { elt = (QElement) xsl.createElementNS(XSLNS, "xsl:stylesheet"); elt.setAttribute("version", "1.0"); elt.setLocation(is.getURL(), is.getUserPath(), 1, 0); elt.setAttribute("resin:stylescript", "true"); Element out = xsl.createElementNS(XSLNS, "xsl:output"); //out.setAttribute("method", "xtp"); //out.setAttribute("disable-output-escaping", "true"); //out.setAttribute("omit-xml-declaration", "true"); elt.appendChild(out); elt.appendChild(top); } // elt.setAttribute("xsl-caucho", "true"); if (encoding != null) { Element out = xsl.createElementNS(XSLNS, "xsl:output"); out.setAttribute("encoding", encoding); elt.insertBefore(out, elt.getFirstChild()); } xsl.appendChild(elt); /* if (dbg.canWrite()) { new XmlPrinter(dbg).printPrettyXml(xsl); } */ return xsl; } /** * Parses in the middle of a node * * @param parent parsed children are attached to the parent node */ private void parseNode(Node parent, String tagEnd, boolean isSpecial, int ch) throws IOException, XslParseException { boolean hasContent = false; text.clear(); if (tagEnd == ">>" && (ch == '\n' || ch == '\r')) ch = read(); while (ch >= 0) { switch (ch) { case '\\': hasContent = true; ch = read(); if (ch == '<') { addText('<'); ch = read(); } else addText('\\'); break; case '<': hasContent = true; ch = read(); if (ch == '/') { ch = readTag(read()); String tag = this.tag.toString(); if (tag.equals(tagEnd)) { ch = skipWhitespace(ch); if (ch != '>') throw error(L.l("expected `{0}' at {1}", ">", badChar(ch))); addText(parent); if (tag.equals("xsl:template")) inTemplate = false; return; } else if (rawText) { addText("</" + tag + ">"); ch = read(); break; } else { throw error(L.l("`</{0}>' has no matching open tag", tag)); } } else if (ch == '#') { addText(parent); ch = parseScriptlet(parent); break; } else if (ch == '?') { addText(parent); ProcessingInstruction pi = parsePi(); parent.appendChild(pi); ch = read(); break; } else if (ch == '!') { addText(parent); ch = parseDecl(parent); break; } else if (ch == '{') { addText(parent); parseValueOf(parent); ch = read(); break; } ch = readTag(ch); String tag = this.tag.toString(); // treat the tag as XML when it has a known prefix or we aren't // in rawText mode if (! rawText && ! tag.equals("") || tag.startsWith("xsl:") || tag.startsWith("jsp:") || tag.startsWith("xtp:") || macros.get(tag) != null) { addText(parent); parseElement(parent, tag, ch, isSpecial); ch = read(); } // otherwise tread the tag as text else { addText("<"); addText(tag); } break; case '>': int ch1 = read(); if (ch1 == '>' && tagEnd == ">>") { if (text.length() > 0 && text.charAt(text.length() - 1) == '\n') text.setLength(text.length() - 1); if (text.length() > 0 && text.charAt(text.length() - 1) == '\r') text.setLength(text.length() - 1); if (! hasContent) { Element elt = xsl.createElementNS(XSLNS, "xsl:text"); parent.appendChild(elt); addText(elt); } else addText(parent); return; } else { hasContent = true; addText('>'); ch = ch1; } break; case '$': hasContent = true; ch = read(); if (ch == '$') { addText('$'); ch = read(); } else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z') { String name = parseName(ch); addText(parent); text.clear(); ch = parseExtension(parent, name); } else if (ch == '(') { addText(parent); Element elt = xsl.createElementNS(XSLNS, "xsl:value-of"); CharBuffer test = CharBuffer.allocate(); lexToRparen(test); elt.setAttribute("select", test.close()); parent.appendChild(elt); ch = read(); } else { addText('$'); ch = read(); } break; case ' ': case '\t': case '\n': case '\r': addText((char) ch); ch = read(); break; case '&': ch = parseEntityReference(); break; default: hasContent = true; if (isSpecial) { parseSpecial(parent, ch); ch = read(); } else { addText((char) ch); ch = read(); } break; } } addText(parent); if (! tagEnd.equals("")) throw error(L.l("expected close of `{0}' (open at {1})", tagEnd, ((CauchoNode) parent).getLine())); } /** * Parses an element. * * @param parent the owning node of the new child * @param name the name of the element * @param ch the current character * @param isSpecial ?? * * @return the new child element */ private Element parseElement(Node parent, String name, int ch, boolean isSpecial) throws IOException, XslParseException { HashMap<String,String> oldNamespaces = _namespaces; QElement element = null; int p = name.indexOf(':'); if (p >= 0) { String prefix = name.substring(0, p); String uri = _namespaces.get(prefix); if (uri != null) element = (QElement) xsl.createElementNS(uri, name); else if (prefix.equals("xsl")) element = (QElement) xsl.createElementNS(XSLNS, name); } try { if (element == null) element = (QElement) xsl.createElement(name); } catch (DOMException e) { throw error(e); } element.setLocation(is.getURL(), is.getUserPath(), line, 0); ch = parseAttributes(null, element, ch, false); if (name.equals("xsl:stylesheet")) { if (element.getAttribute("parsed-content").equals("false")) { rawText = true; Element child = xsl.createElementNS(XSLNS, "xsl:output"); child.setAttribute("disable-output-escaping", "yes"); element.appendChild(child); } } if (rawText && (name.startsWith("xsl") || name.startsWith("xtp")) && element.getAttribute("xml:space").equals("")) element.setAttribute("xml:space", "default"); if (name.equals("xsl:template")) { inTemplate = true; String macroName = element.getAttribute("name"); if (! macroName.equals("")) macros.put(macroName, macroName); } String oldMode = defaultMode; if (name.equals("xtp:mode")) { defaultMode = element.getAttribute("mode"); } else { parent.appendChild(element); parent = element; } if (ch == '>') { parseNode(parent, name, isSpecial && name.equals("xsl:stylesheet"), read()); } else if (ch == '/') { if ((ch = read()) != '>') throw error(L.l("expected `{0}' at {1}", ">", badChar(ch))); } else throw error(L.l("expected `{0}' at {1}", ">", badChar(ch))); defaultMode = oldMode; _namespaces = oldNamespaces; return element; } /** * Parses an entity reference, e.g. &lt; */ private int parseEntityReference() throws IOException, XslParseException { int ch = read(); if (ch == '#') { int code = 0; ch = read(); if (ch == 'x') { for (ch = read(); ch > 0 && ch != ';'; ch = read()) { if (ch >= '0' && ch <= '9') code = 16 * code + ch - '0'; else if (ch >= 'a' && ch <= 'f') code = 16 * code + ch - 'a' + 10; else if (ch >= 'A' && ch <= 'F') code = 16 * code + ch - 'A' + 10; else break; } if (ch == ';') { addText((char) code); return read(); } else { addText("&#x"); addText(String.valueOf(code)); return ch; } } else { for (; ch >= '0' && ch <= '9'; ch = read()) { code = 10 * code + ch - '0'; } } if (ch == ';') { addText((char) code); return read(); } else { addText("&#"); addText(String.valueOf(code)); return ch; } } CharBuffer cb = CharBuffer.allocate(); for (; ch >= 'a' && ch <= 'z'; ch = read()) cb.append((char) ch); if (ch != ';') { addText('&'); addText(cb.close()); } else if (cb.matches("lt")) { addText('<'); return read(); } else if (cb.matches("gt")) { addText('>'); return read(); } else if (cb.matches("amp")) { addText('&'); return read(); } else if (cb.matches("quot")) { addText('"'); return read(); } else if (cb.matches("apos")) { addText('\''); return read(); } else { addText('&'); addText(cb.close()); } return ch; } /** * Parses the contents of a '<#' section. */ private int parseScriptlet(Node parent) throws IOException, XslParseException { String filename = is.getUserPath(); int line = this.line; int ch = read(); QNode node; if (ch == '=') { node = (QNode) xsl.createElementNS(XTPNS, "xtp:expression"); ch = read(); } else if (ch == '!') { node = (QNode) xsl.createElementNS(XTPNS, "xtp:declaration"); ch = read(); } else if (ch == '@') { parseDirective(parent); return read(); } else node = (QNode) xsl.createElementNS(XTPNS, "xtp:scriptlet"); node.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(node); text.clear(); while (ch >= 0) { if (ch == '#') { ch = read(); if (ch == '>') break; else addText('#'); } else { addText((char) ch); ch = read(); } } node.appendChild(xsl.createTextNode(text.toString())); text.clear(); return read(); } /** * parses an xtp directive: <#@ */ private void parseDirective(Node parent) throws IOException, XslParseException { int ch; ch = skipWhitespace(read()); ch = readTag(ch); String name = tag.toString(); if (! name.equals("page") && ! name.equals("cache")) throw error(L.l("unknown directive `{0}'", name)); QElement elt = (QElement) xsl.createElementNS(XTPNS, "xtp:directive." + name); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(elt); ch = parseAttributes(parent, elt, ch, true); if (ch != '#') throw error(L.l("expected `{0}' at {1}", "#", badChar(ch))); if ((ch = read()) != '>') throw error(L.l("expected `{0}' at {1}", ">", badChar(ch))); if (name.equals("page")) { String contentType = elt.getAttribute("contentType"); if (! contentType.equals("")) parseContentType(parent, contentType); } } private int parseStatement(Node parent, int ch) throws IOException, XslParseException { ch = skipWhitespace(ch); if (ch == '$') { ch = read(); if (XmlChar.isNameStart(ch)) { String name = parseName(ch); return parseExtension(parent, name); } else if (ch == '(') { Element elt = xsl.createElementNS(XSLNS, "xsl:value-of"); CharBuffer test = CharBuffer.allocate(); lexToRparen(test); elt.setAttribute("select", test.close()); parent.appendChild(elt); return read(); } else throw error(L.l("expected statement at {0}", badChar(ch))); } else if (ch == '<') { parseBlock(parent, ch); return read(); } else if (ch == ';') return read(); else throw error(L.l("expected statement at {0}", badChar(ch))); } private int parseExtension(Node parent, String name) throws IOException, XslParseException { int ch = read(); if (name.equals("if")) return parseIf(parent, ch); String arg = (String) _xslCommands.get(name); if (arg != null) { QElement elt = (QElement) xsl.createElementNS(XSLNS, "xsl:" + name); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(elt); ch = skipWhitespace(ch); if (ch == '(') { parseArgs(elt, arg); ch = skipWhitespace(read()); } return parseStatement(elt, ch); } arg = (String) _xtpCommands.get(name); if (arg != null) { QElement elt = (QElement) xsl.createElement("xtp:" + name); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(elt); ch = skipWhitespace(ch); if (ch == '(') { parseArgs(elt, arg); ch = skipWhitespace(read()); } return parseStatement(elt, ch); } ch = skipWhitespace(ch); if (ch == '=') { QElement elt = (QElement) xsl.createElement("xtp:assign"); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); elt.setAttribute("name", name.intern()); parent.appendChild(elt); ch = skipWhitespace(read()); if (ch != '$') return parseStatement(elt, ch); else if ((ch = read()) != '(') { peek = ch; return parseStatement(elt, ch); } else { CharBuffer test = CharBuffer.allocate(); lexToRparen(test); elt.setAttribute("select", test.close()); return read(); } } QElement elt = (QElement) xsl.createElement(name); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(elt); if (ch == '(') { parseArgs(elt, arg); ch = skipWhitespace(read()); } return parseStatement(elt, ch); } private int parseIf(Node parent, int ch) throws IOException, XslParseException { QElement choose = (QElement) xsl.createElementNS(XSLNS, "xsl:choose"); choose.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(choose); while (true) { lexExpect(ch, '('); CharBuffer test = CharBuffer.allocate(); lexToRparen(test); QElement elt = (QElement) xsl.createElementNS(XSLNS, "xsl:when"); choose.appendChild(elt); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); elt.setAttribute("test", test.close()); ch = parseStatement(elt, skipWhitespace(read())); ch = skipWhitespace(ch); if (ch != '$') return ch; ch = read(); if (! XmlChar.isNameStart(ch)) { peek = ch; return '$'; } String name = parseName(ch); if (! name.equals("else")) return parseExtension(parent, name); ch = skipWhitespace(read()); if (ch == '<') { elt = (QElement) xsl.createElementNS(XSLNS, "xsl:otherwise"); choose.appendChild(elt); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); return parseStatement(elt, skipWhitespace(ch)); } name = parseName(read()); if (! name.equals("if")) throw error(L.l("expected $if at `${0}'", name)); ch = read(); } } private String parseName(int ch) throws IOException, XslParseException { CharBuffer cb = CharBuffer.allocate(); for (; XmlChar.isNameChar(ch); ch = read()) cb.append((char) ch); peek = ch; return cb.close(); } private void parseArgs(Element elt, String arg) throws IOException, XslParseException { CharBuffer cb = CharBuffer.allocate(); String key = null; boolean isFirst = true; int ch; for (ch = read(); ch >= 0 && ch != ')'; ch = read()) { cb.append((char) ch); switch (ch) { case '(': lexToRparen(cb); cb.append(')'); break; case '"': case '\'': lexString(cb, ch); break; case '=': ch = read(); if (ch == '>') { cb.setLength(cb.length() - 1); key = cb.toString().trim(); cb.clear(); } else { peek = ch; } break; case ',': cb.setLength(cb.length() - 1); if (key != null) elt.setAttribute(key, cb.toString()); else if (arg != null && isFirst) elt.setAttribute(arg, cb.toString()); else throw error(L.l("unexpected arg `{0}'", cb)); cb.clear(); isFirst = false; key = null; break; } } if (ch != ')') throw error(L.l("expected `{0}' at {1}", ")", badChar(ch))); if (key != null) elt.setAttribute(key, cb.close()); else if (arg != null && cb.length() > 0 && isFirst) elt.setAttribute(arg, cb.close()); else if (cb.length() > 0) throw error(L.l("unexpected arg `{0}'", cb)); } /** * Scan the buffer up to the right parenthesis. * * @param cb buffer holding the contents. */ private void lexToRparen(CharBuffer cb) throws IOException, XslParseException { String filename = getFilename(); int line = getLine(); int ch; for (ch = read(); ch >= 0 && ch != ')'; ch = read()) { cb.append((char) ch); switch (ch) { case '(': lexToRparen(cb); cb.append(')'); break; case '"': case '\'': lexString(cb, ch); break; } } if (ch != ')') throw error(L.l("expected `{0}' at {1}. Open at {2}", ")", badChar(ch), filename + ":" + line)); } private void lexString(CharBuffer cb, int end) throws IOException, XslParseException { int ch; for (ch = read(); ch >= 0 && ch != end; ch = read()) { cb.append((char) ch); } if (ch != end) throw error(L.l("expected `{0}' at {1}", "" + (char) end, badChar(ch))); cb.append((char) end); } private void lexExpect(int ch, int match) throws IOException, XslParseException { for (; XmlChar.isWhitespace((char) ch); ch = read()) { } if (ch != match) throw error(L.l("expected `{0}' at {1}", "" + (char) match, badChar(ch))); } private CharScanner wsScanner = new CharScanner(" \t"); private CharScanner delimScanner = new CharScanner(" \t;="); /** * parse the content-type, possibly changing character-encoding. */ private void parseContentType(Node parent, String contentType) throws IOException, XslParseException { CharCursor cursor = new StringCharCursor(contentType); CharBuffer buf = new CharBuffer(); wsScanner.skip(cursor); delimScanner.scan(cursor, buf); if (buf.length() <= 0) return; Element output = xsl.createElementNS(XSLNS, "xsl:output"); parent.appendChild(output); output.setAttribute("media-type", buf.toString()); delimScanner.skip(cursor); buf.clear(); delimScanner.scan(cursor, buf); wsScanner.skip(cursor); if (cursor.current() == '=' && buf.toString().equals("charset")) { delimScanner.skip(cursor); buf.clear(); delimScanner.scan(cursor, buf); if (buf.length() > 0) { output.setAttribute("encoding", Encoding.getMimeName(buf.toString())); is.setEncoding(buf.toString()); } } } /** * Parses the attributes of an element. * * @param parent the elements parent. * @param elt the element itself * @param ch the next character * @param isDirective true if this is a special directive * * @return the next character */ private int parseAttributes(Node parent, Element elt, int ch, boolean isDirective) throws IOException, XslParseException { HashMap<String,String> newNamespaces = null; ch = skipWhitespace(ch); while (XmlChar.isNameStart(ch)) { ch = readTag(ch); String name = tag.toString(); ch = skipWhitespace(ch); String value = null; if (ch == '=') { ch = skipWhitespace(read()); ch = readValue(ch); ch = skipWhitespace(ch); value = tag.toString(); } int p; if (isDirective && name.equals("import")) { Element copy = (Element) elt.cloneNode(false); copy.setAttribute(name, value); parent.appendChild(copy); } else if (name.startsWith("xmlns")) { QElement qElt = (QElement) elt; if (newNamespaces == null) { newNamespaces = new HashMap<String,String>(_namespaces); _namespaces = newNamespaces; } String prefix; if (name.startsWith("xmlns:")) prefix = name.substring(6); else prefix = ""; _namespaces.put(prefix, value); qElt.setAttributeNS(XMLNS, name, value); // backpatch if modify own name if (prefix != "" && qElt.getNodeName().startsWith(prefix)) { QDocument doc = (QDocument) xsl; QName newName = doc.createName(value, qElt.getNodeName()); qElt.setName(newName); } } else if ((p = name.indexOf(':')) >= 0) { String prefix = name.substring(0, p); String uri = _namespaces.get(prefix); if (uri != null) { QElement qElt = (QElement) elt; qElt.setAttributeNS(uri, name, value); } else elt.setAttribute(name, value); } else { elt.setAttribute(name, value); } } return ch; } /** * Parses a processing instruction */ private ProcessingInstruction parsePi() throws IOException, XslParseException { int ch = read(); if (! XmlChar.isNameStart(ch)) throw error(L.l("expected name at {0}", badChar(ch))); ch = readTag(ch); String name = tag.toString(); text.clear(); while (ch >= 0) { if (ch == '?') { if ((ch = read()) == '>') { ProcessingInstruction pi; pi = xsl.createProcessingInstruction(name, text.toString()); text.clear(); return pi; } else addText('?'); } else { addText((char) ch); ch = read(); } } throw error(L.l("expected `{0}' at {1}", ">", badChar(-1))); } private int parseDecl(Node parent) throws IOException, XslParseException { int ch = read(); if (ch == '[') { if ((ch = read()) != 'C') { addText("<!["); return ch; } else if ((ch = read()) != 'D') { addText("<![C"); return ch; } else if ((ch = read()) != 'A') { addText("<![CD"); return ch; } else if ((ch = read()) != 'T') { addText("<![CDA"); return ch; } else if ((ch = read()) != 'A') { addText("<![CDAT"); return ch; } else if ((ch = read()) != '[') { addText("<![CDATA"); return ch; } else { ch = read(); while (ch > 0) { if (ch == ']') { ch = read(); while (ch == ']') { if ((ch = read()) == '>') return read(); else addText(']'); } addText(']'); } else { addText((char) ch); ch = read(); } } return ch; } } if (ch != '-') { addText("<!"); return ch; } if ((ch = read()) != '-') { addText("<!-"); return ch; } while (ch >= 0) { if ((ch = read()) == '-') { ch = read(); while (ch == '-') { if ((ch = read()) == '>') return read(); } } } throw error(L.l("expected `{0}' at {1}", "-->", badChar(-1))); } /** * Parses the shortcut for valueOf <{...}> */ private void parseValueOf(Node parent) throws IOException, XslParseException { int ch = read(); while (ch >= 0) { if (ch == '}') { ch = read(); if (ch == '>') { QElement elt; elt = (QElement) xsl.createElementNS(XSLNS, "xsl:value-of"); elt.setAttribute("select", text.toString()); elt.setLocation(is.getURL(), is.getUserPath(), line, 0); parent.appendChild(elt); text.clear(); return; } else addText('}'); } else { addText((char) ch); ch = read(); } } } /** * parses top-level templates: * * pattern << ... >> -- <xsl:template match='pattern'>...</xsl:template> * pattern <# ... #> -- <xsl:template match='pattern'> * <xtp:scriptlet>...</xtp:scriptlet> * </xsl:template> * pattern <#= ... #> -- <xsl:template match='pattern'> * <xtp:expression>...</xtp:expression> * </xsl:template> */ private void parseSpecial(Node parent, int ch) throws IOException, XslParseException { char tail = '#'; String element = "xtp:scriptlet"; text.clear(); String filename = is.getUserPath(); int line = this.line; while (ch >= 0) { if (ch == '<') { filename = is.getUserPath(); line = this.line; ch = read(); if (ch == '#') { tail = '#'; ch = read(); if (ch == '=') { ch = read(); element = "xtp:expression"; } break; } else if (ch == '<') { tail = '>'; break; } else if (ch == '\\') { addText((char) read()); ch = read(); } } else { addText((char) ch); ch = read(); } } while (text.length() > 0 && Character.isSpace(text.charAt(text.length() - 1))) { text.setLength(text.length() - 1); } QElement template = (QElement) xsl.createElementNS(XSLNS, "xsl:template"); parent.appendChild(template); String match = text.toString(); template.setAttribute("match", match); boolean isName = true; for (int i = 0; i < match.length(); i++) { if (! XmlChar.isNameChar(match.charAt(i))) { isName = false; break; } } if (isName && false) // XXX: problems template.setAttribute("name", match); if (defaultMode != null) template.setAttribute("mode", defaultMode); template.setLocation(filename, filename, line, 0); text.clear(); inTemplate = true; if (tail == '>') { if (rawText) template.setAttribute("xml:space", "preserve"); parseNode(template, ">>", false, read()); inTemplate = false; return; } QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element); scriptlet.setLocation(filename, filename, line, 0); while (ch >= 0) { if (ch == tail) { ch = read(); if (ch == '>') break; else addText(tail); } else { addText((char) ch); ch = read(); } } scriptlet.appendChild(xsl.createTextNode(text.toString())); template.appendChild(scriptlet); text.clear(); inTemplate = false; } private void parseBlock(Node parent, int ch) throws IOException, XslParseException { char tail = '#'; String element = "xtp:scriptlet"; for (; XmlChar.isWhitespace((char) ch); ch = read()) { } if (ch == ';') return; if (ch != '<') throw error(L.l("expected `{0}' at {1}", "<", badChar(ch))); String filename = is.getUserPath(); int line = this.line; ch = read(); if (ch == '#') { tail = '#'; ch = read(); if (ch == '=') { ch = read(); element = "xtp:expression"; } } else if (ch == '<') { tail = '>'; } else throw error(L.l("expected block at {1}", "block", badChar(ch))); if (tail == '>') { if (rawText) ((Element) parent).setAttribute("xml:space", "preserve"); parseNode(parent, ">>", false, read()); return; } QNode scriptlet = (QNode) xsl.createElementNS(XTPNS, element); scriptlet.setLocation(filename, filename, line, 0); while (ch >= 0) { if (ch == tail) { ch = read(); if (ch == '>') break; else addText(tail); } else { addText((char) ch); ch = read(); } } scriptlet.appendChild(xsl.createTextNode(text.toString())); parent.appendChild(scriptlet); text.clear(); } private void addText(char ch) { if (text.length() == 0) { if (ch == '\n') textLine = line - 1; else textLine = line; } text.append(ch); } private void addText(String s) { if (text.length() == 0) textLine = line; text.append(s); } private int skipWhitespace(int ch) throws IOException { for (; XmlChar.isWhitespace(ch); ch = read()) { } return ch; } private int readTag(int ch) throws IOException { tag.clear(); for (; XmlChar.isNameChar(ch); ch = read()) tag.append((char) ch); return ch; } /** * Scans an attribute value, storing the results in <code>tag</code>. * * @param ch the current read character. * @return the next read character after the value. */ private int readValue(int ch) throws IOException, XslParseException { tag.clear(); if (ch == '\'') { for (ch = read(); ch >= 0 && ch != '\''; ch = read()) { if (ch == '&') { ch = parseEntityReference(); tag.append(text); text.clear(); unread(ch); } else tag.append((char) ch); } if (ch != '\'') throw error(L.l("expected `{0}' at {1}", "'", badChar(ch))); return read(); } else if (ch == '"') { for (ch = read(); ch >= 0 && ch != '"'; ch = read()) { if (ch == '&') { ch = parseEntityReference(); tag.append(text); text.clear(); unread(ch); } else tag.append((char) ch); } if (ch != '\"') throw error(L.l("expected `{0}' at {1}", "\"", badChar(ch))); return read(); } else if (XmlChar.isNameChar(ch)) { for (; XmlChar.isNameChar(ch); ch = read()) tag.append((char) ch); return ch; } else throw error(L.l("expected attribute value at {0}", badChar(ch))); } /** * Add the current accumulated text to the parent as a text node. * * @param parent node to contain the text. */ private void addText(Node parent) { if (text.getLength() == 0) { } else { Text textNode = (Text) xsl.createTextNode(text.toString()); QAbstractNode node = (QAbstractNode) textNode; node.setLocation(is.getURL(), is.getUserPath(), textLine, 0); parent.appendChild(textNode); } text.clear(); } /** * Returns an error including the current filename and line in emacs style. * * @param message the error message. */ private XslParseException error(String message) { return new XslParseException(getFilename() + ":" + getLine() + ": " + message); } /** * Returns an error including the current filename and line in emacs style. * * @param message the error message. */ private XslParseException error(Exception e) { if (e.getMessage() != null) return new XslParseException(getFilename() + ":" + getLine() + ": " + e.getMessage()); else return new XslParseException(getFilename() + ":" + getLine() + ": " + e); } /** * Return the source filename. */ private String getFilename() { return is.getPath().getUserPath(); } /** * Return the source line. */ private int getLine() { return line; } /** * Returns a string for the error character. */ private String badChar(int ch) { if (ch < 0) return L.l("end of file"); else if (ch == '\n' || ch == '\r') return L.l("end of line"); else return "`" + (char) ch + "'"; } /** * Reads a character from the stream, keeping track of newlines. */ public int read() throws IOException { if (peek >= 0) { int ch = peek; peek = -1; return ch; } int ch = is.readChar(); if (ch == '\r') { if ((ch = is.readChar()) != '\n') { if (ch >= 0) { if (ch == '\r') peek = '\n'; else peek = ch; } } ch = '\n'; } if (ch == '\n') line++; return ch; } void unread(int ch) { peek = ch; } static { _xslCommands = new HashMap<String,String>(); _xslCommands.put("apply-templates", "select"); _xslCommands.put("call-template", "name"); _xslCommands.put("apply-imports", ""); _xslCommands.put("for-each", "select"); _xslCommands.put("value-of", "select"); _xslCommands.put("copy-of", "select"); _xslCommands.put("number", "value"); _xslCommands.put("choose", ""); _xslCommands.put("when", "test"); _xslCommands.put("otherwise", ""); _xslCommands.put("if", "test"); _xslCommands.put("text", ""); _xslCommands.put("copy", ""); _xslCommands.put("variable", "name"); _xslCommands.put("param", "name"); _xslCommands.put("with-param", "name"); _xslCommands.put("message", ""); _xslCommands.put("fallback", ""); _xslCommands.put("processing-instruction", "name"); _xslCommands.put("comment", ""); _xslCommands.put("element", "name"); _xslCommands.put("attribute", "name"); _xslCommands.put("import", "href"); _xslCommands.put("include", "href"); _xslCommands.put("strip-space", "elements"); _xslCommands.put("preserve-space", "elements"); _xslCommands.put("output", ""); _xslCommands.put("key", ""); _xslCommands.put("decimal-format", ""); _xslCommands.put("attribute-set", "name"); _xslCommands.put("variable", "name"); _xslCommands.put("param", "name"); _xslCommands.put("template", "match"); _xslCommands.put("namespace-alias", ""); // two args // xslt 2.0 _xslCommands.put("result-document", "href"); _xtpCommands = new HashMap<String,String>(); _xtpCommands.put("while", "test"); _xtpCommands.put("expression", "expr"); _xtpCommands.put("expr", "expr"); _xtpCommands.put("scriptlet", ""); _xtpCommands.put("declaration", ""); _xtpCommands.put("directive.page", ""); _xtpCommands.put("directive.cache", ""); } }