public final void testMisplacedPlaceholderContent() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:message name=\"m\">\n" + " \n" + " \n" + " </ihtml:message>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:message name=\"m\">\n" + " <ihtml:ph name=\"p\">Hi</ihtml:ph>\n" + " <ihtml:eph>There</ihtml:eph>\n" + " </ihtml:message>\n" + "</ihtml:template>", new Message( IhtmlMessageType.INAPPROPRIATE_CONTENT, FilePosition.instance(is, 3, 88, 24, 2), MessagePart.Factory.valueOf("ph")), new Message( IhtmlMessageType.INAPPROPRIATE_CONTENT, FilePosition.instance(is, 4, 117, 16, 5), MessagePart.Factory.valueOf("eph"))); }
public final void testBadCall() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"hi\">\n" + " \n" + "</ihtml:template>", "" + "<ihtml:template name='hi' formals=''>\n" + " <ihtml:call foo='bar' baz:boo='far'/>\n" + "</ihtml:template>", new Message( IhtmlMessageType.MISSING_ATTRIB, FilePosition.instance(is, 2, 41, 3, 37), elKey("ihtml:call"), attrKey("ihtml:call", "ihtml:template")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 2, 63, 25, 13), elKey("ihtml:call"), AttribKey.forAttribute( new Namespaces(Namespaces.XML_SPECIAL, "baz", "unknown:///baz"), elKey("ihtml:call"), "baz:boo"), MessagePart.Factory.valueOf("far")), new Message( MessageType.NO_SUCH_NAMESPACE, FilePosition.fromLinePositions(is, 2, 25, 2, 32), MessagePart.Factory.valueOf("baz"), MessagePart.Factory.valueOf("baz:boo"))); }
public final void testTemplateNames() throws Exception { runTest( "<ihtml:template formals=\"x\" name=\"hi\"></ihtml:template>", "" + "<ihtml:template formals='x' name='hi'>" + "<ihtml:template name='3nested' formals='a,,x,if,3' zoinks='ahoy'/>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 55, 55, 14), elKey("ihtml:template"), attrKey("ihtml:template", "name"), MessagePart.Factory.valueOf("3nested")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 70, 70, 19), elKey("ihtml:template"), attrKey("ihtml:template", "formals"), MessagePart.Factory.valueOf("a,,x,if,3")), new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 90, 90, 13), elKey("ihtml:template"), attrKey("ihtml:template", "zoinks"), MessagePart.Factory.valueOf("ahoy"))); }
public final void testBadPlaceholderName() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:dynamic expr=\"planet\"></ihtml:dynamic>" + "!" + "</ihtml:message>" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name=\"hi\">" + "Hello " + "<ihtml:ph name=\"if\"/>" + "<ihtml:dynamic expr=\"planet\"/>" + "<ihtml:eph/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 78, 78, 9), elKey("ihtml:ph"), attrKey("ihtml:ph", "name"), MessagePart.Factory.valueOf("if")), new Message( IhtmlMessageType.ORPHANED_PLACEHOLDER_END, FilePosition.instance(is, 1, 119, 119, 12))); }
public final void testMisplacedElementAndAttribute() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element></ihtml:element>\n" + " <ihtml:message name=\"m\">\n" + " \n" + " <ihtml:ph name=\"ph\"></ihtml:ph>\n" + " \n" + " <ihtml:eph></ihtml:eph>\n" + " </ihtml:message>\n" + " <ihtml:do init=\"maybe\">\n" + " <ihtml:element></ihtml:element>\n" + " <ihtml:else></ihtml:else>\n" + " <ihtml:element></ihtml:element>\n" + " </ihtml:do>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element/>\n" // OK inside templates + " <ihtml:message name=\"m\">\n" + " <ihtml:element/>\n" // But not inside messages + " <ihtml:ph name=\"ph\"/>\n" + " <ihtml:element/>\n" // Not even inside messages + " <ihtml:eph/>\n" + " </ihtml:message>\n" + " <ihtml:do init=\"maybe\">\n" + " <ihtml:element/>\n" // OK inside a conditional + " <ihtml:else/>\n" + " <ihtml:element/>\n" // OK inside a conditional's alternate + " </ihtml:do>\n" + "</ihtml:template>", new Message( IhtmlMessageType.MISPLACED_ELEMENT, FilePosition.instance(is, 4, 88, 5, 16), MessagePart.Factory.valueOf("ihtml:element"), MessagePart.Factory.valueOf("ihtml:message")), new Message( IhtmlMessageType.MISPLACED_ELEMENT, FilePosition.instance(is, 6, 137, 7, 16), MessagePart.Factory.valueOf("ihtml:element"), MessagePart.Factory.valueOf("ihtml:message"))); }
private static int parseEscapeBody(CharProducer cp, int start) throws ParseException { // unicode \\{h}{1,6}(\r\n|[ \t\r\n\f])? // escape {unicode}|\\[^\r\n\f0-9a-f] int limit = cp.getLimit(); char[] buf = cp.getBuffer(); if (start == limit) { throw new ParseException( new Message( MessageType.EXPECTED_TOKEN, cp.filePositionForOffsets(start, start), MessagePart.Factory.valueOf("<hex-digit>"), MessagePart.Factory.valueOf("<end-of-input>"))); } char ch = buf[start]; if (CssLexer.isHexChar(ch)) { int end = start + 1; for (int i = 5; --i >= 0; ++end) { if (end == limit) { break; } ch = buf[end]; if (!CssLexer.isHexChar(ch)) { break; } } if (end < limit && CssLexer.isSpaceChar(ch = buf[end])) { ++end; if ('\r' == ch && end < limit && '\n' == buf[end]) { ++end; } } return end; } else if (isLineBreak(ch)) { throw new ParseException( new Message( MessageType.UNRECOGNIZED_ESCAPE, cp.filePositionForOffsets(start, start), MessagePart.Factory.valueOf(String.valueOf(ch)))); } else { return start + 1; } }
public final void testIhtmlElementInMessageOutsidePlaceholder() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\"></ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">" + "<ihtml:message name='SayHowdy'>" + "Hello " + "<ihtml:dynamic expr=\"planet\"/>!" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.IHTML_IN_MESSAGE_OUTSIDE_PLACEHOLDER, FilePosition.instance(is, 1, 74, 74, 30), MessagePart.Factory.valueOf("dynamic"))); }
public final void testMisnamedMessage() throws Exception { runTest( "<ihtml:template formals=\"a b\" name=\"t\"></ihtml:template>", "" + "<ihtml:template formals=\"a b\" name=\"t\">" + "<ihtml:message name=\"x__\">" + "Hello" + "</ihtml:message>" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 1, 55, 55, 10), elKey("ihtml:message"), attrKey("ihtml:message", "name"), MessagePart.Factory.valueOf("x__"))); }
private static int parseString(CharProducer cp, int start) throws ParseException { int limit = cp.getLimit(); if (start == limit) { return -1; } char[] buf = cp.getBuffer(); char ch = buf[start]; if (ch != '\'' && ch != '"') { return -1; } // {string} STRING // string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" // string2 \'([^\n\r\f\\']|\\{nl}|{escape})*\' // string {string1}|{string2} char delim = ch; int end = start + 1; while (end < limit) { ch = buf[end]; ++end; // escape {unicode}|\\[^\r\n\f0-9a-f] // nl \n|\r\n|\r|\f if (delim == ch) { return end; } else if (ch == '\\') { if (end < limit && isLineBreak(ch = buf[end])) { ++end; if (ch == '\r' && end < limit && buf[end] == '\n') { ++end; } } else { end = parseEscapeBody(cp, end); } } else if (isLineBreak(ch)) { throw new ParseException( new Message( MessageType.MALFORMED_STRING, cp.filePositionForOffsets(end - 1, end - 1), MessagePart.Factory.valueOf("" + ch))); } } throw new ParseException( new Message(MessageType.UNTERMINATED_STRING_TOKEN, cp.filePositionForOffsets(start, end))); }
/** * Introduce a new declaration which will mask any declaration with the same name in the {@link * #getParentContext} context. */ public VarInfo<NAME, BINDING> declare(NAME origName, FilePosition declSite) throws RedeclarationException { VarInfo<NAME, BINDING> d = vars.get(origName); if (d == null) { String newName = nameGenerator.next(); VarInfo<NAME, BINDING> vi = new VarInfo<NAME, BINDING>(origName, newName, declSite); vars.put(origName, vi); return vi; } else { FilePosition dPos = d.declaredAt; throw new RedeclarationException( new Message( RewriterMessageType.CANNOT_REDECLARE_VAR, declSite, MessagePart.Factory.valueOf(origName.toString()), dPos)); } }
private boolean parseInputs(Collection<URI> inputs, PluginCompiler pluginc) { boolean parsePassed = true; for (URI input : inputs) { try { ParseTreeNode parseTree = parseInput(input); if (null != parseTree) { pluginc.addInput(new AncestorChain<ParseTreeNode>(parseTree)); } } catch (ParseException ex) { ex.toMessageQueue(mq); parsePassed = false; } catch (IOException ex) { mq.addMessage(MessageType.IO_ERROR, MessagePart.Factory.valueOf(ex.toString())); parsePassed = false; } } return parsePassed; }
public final void testBadAttr() throws Exception { runTest( "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " \n" + " <div><ihtml:element>p</ihtml:element></div>\n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"t\">\n" + " <ihtml:element bogus=\"\">p</ihtml:element>\n" + " <div><ihtml:element>p</ihtml:element></div>\n" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 2, 55, 18, 8), elKey("ihtml:element"), attrKey("ihtml:element", "bogus"), MessagePart.Factory.valueOf(""))); }
public final void testContentType() throws Exception { // The callingContext attribute is set by a later pass, so make sure it // can't be passed in. runTest( "" + "<ihtml:template formals=\"\" name=\"main\">\n" + " \n" + "</ihtml:template>", "" + "<ihtml:template formals=\"\" name=\"main\">\n" + " <ihtml:template formals=\"\" name=\"sub\"\n" + " " + IHTML.CALLING_CONTEXT_ATTR + "=\"div\">\n" + " </ihtml:template>\n" + "</ihtml:template>", new Message( IhtmlMessageType.BAD_ATTRIB, FilePosition.instance(is, 3, 4, 4, 20), elKey("ihtml:template"), attrKey("ihtml:template", "callingContext"), MessagePart.Factory.valueOf("div"))); }
private static int parseRange(CharProducer cp, int start) throws ParseException { // range \?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}\ // (\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))) // This method also handles {h}{1,6}-{h}{1,6} char[] buf = cp.getBuffer(); int limit = cp.getLimit(); int end = start; int len = 6; boolean isRange = end < limit && buf[end] == '?'; if (isRange) { while (end < limit && '?' == buf[end] && --len >= 0) { ++end; } } while (end < limit && CssLexer.isHexChar(buf[end]) && --len >= 0) { ++end; } if (!isRange) { if (end == limit || '-' != buf[end]) { throw new ParseException( new Message( MessageType.EXPECTED_TOKEN, cp.filePositionForOffsets(end, end), MessagePart.Factory.valueOf("-"), toMessagePart(cp, end))); } ++end; len = 6; while (end < limit && '?' == buf[end] && --len >= 0) { ++end; } while (end < limit && CssLexer.isHexChar(buf[end]) && --len >= 0) { ++end; } } return end != start ? end : -1; }
/** * Only handles the case where num does not start with a dot since it is hard to distinguish a "." * token from a number token with 1 char lookahead. */ private static int parseNum(CharProducer cp, int start) throws ParseException { // num [0-9]+|[0-9]*"."[0-9]+ int end = parseInt(cp, start); assert end >= 0; int limit = cp.getLimit(); char[] buf = cp.getBuffer(); if (end < limit && '.' == buf[end]) { ++end; char ch; // By CSS rules, 0. is an invalid number. if (end == limit || (ch = buf[end]) < '0' || ch > '9') { throw new ParseException( new Message( MessageType.MALFORMED_NUMBER, cp.filePositionForOffsets(start, end), MessagePart.Factory.valueOf(cp.toString(start, end)))); } return parseInt(cp, end); } return end; }
private void produce() throws ParseException { if (null != pending) { return; } if (cp.isEmpty()) { return; } char[] buf = cp.getBuffer(); final int start = cp.getOffset(); int limit = cp.getLimit(); int end = start + 1; CssTokenType type; char ch = buf[start]; int identEnd; if (CssLexer.isSpaceChar(ch)) { // [ \t\r\n\f]+ S end = parseWhitespace(buf, end, limit); type = CssTokenType.SPACE; } else if (ch == '/') { if (end < limit && buf[end] == '*') { // \/\*[^*]*\*+([^/*][^*]*\*+)*\/ /* ignore comments */ int state = 0; // 0 - start, 1 - in comment, 2 - saw, 3 - done do { if (end == limit) { break; } ch = buf[end]; switch (state) { case 0: state = 1; break; case 1: if (ch == '*') { state = 2; } break; case 2: if (ch == '/') { state = 3; } else if (ch != '*') { state = 1; } break; } ++end; } while (state != 3); if (state != 3) { throw new ParseException( new Message( MessageType.UNTERMINATED_COMMENT_TOKEN, cp.filePositionForOffsets(start, end))); } type = CssTokenType.COMMENT; } else if (end < limit && buf[end] == '/') { do { if (++end == limit) { break; } ch = buf[end]; // Line comment does not contain the newline character that ends it // since we don't want to break \r\n sequences across two tokens, // and for consistency with JavaScript conventions which exclude the // newline from the line comment token. if (ch == '\r' || ch == '\n') { break; } } while (true); type = CssTokenType.COMMENT; FilePosition commentPos = cp.filePositionForOffsets(start, end); mq.addMessage(MessageType.INVALID_CSS_COMMENT, commentPos); } else { // *yytext type = CssTokenType.PUNCTUATION; } } else if ('~' == ch || '|' == ch) { if (end < limit && '=' == buf[end]) { // "~=" INCLUDES // "|=" DASHMATCH ++end; } else { // . *yytext } type = CssTokenType.PUNCTUATION; } else if (ch == '\'' || ch == '"') { end = parseString(cp, start); type = CssTokenType.STRING; } else if (ch == '@') { identEnd = parseIdent(cp, end); if (identEnd != -1) { // "@import" IMPORT_SYM // "@page" PAGE_SYM // "@media" MEDIA_SYM // "@font-face" FONT_FACE_SYM // "@charset " CHARSET_SYM // "@"{ident} ATKEYWORD type = CssTokenType.SYMBOL; end = identEnd; // In http://www.w3.org/TR/CSS21/grammar.html, the CHARSET_SYM is // allowed to match only "@charset " if ((end - start) == 8 && parseMatch(cp, start, "@charset ") > 0) { ++end; } } else { // . *yytext type = CssTokenType.PUNCTUATION; } } else if (ch == '!') { // "!{w}important" IMPORTANT_SYM // handled by token joining at a later pass // . *yytext type = CssTokenType.PUNCTUATION; } else if (ch == '#') { int nameEnd = parseName(cp, end); if (nameEnd >= 0) { // "#"{name} HASH type = CssTokenType.HASH; end = nameEnd; } else { // . *yytext type = CssTokenType.PUNCTUATION; } } else if (ch == '<' || ch == '-') { // "<!--" CDO // "-->" CDC int tailEnd = parseMatch(cp, end, ch == '<' ? "!--" : "->"); if (tailEnd >= 0) { end = tailEnd; } type = CssTokenType.PUNCTUATION; } else if ((ch >= '0' && ch <= '9') || '.' == ch) { // {num}em EMS // {num}ex EXS // {num}px LENGTH // {num}cm LENGTH // {num}mm LENGTH // {num}in LENGTH // {num}pt LENGTH // {num}pc LENGTH // {num}deg ANGLE // {num}rad ANGLE // {num}grad ANGLE // {num}ms TIME // {num}s TIME // {num}Hz FREQ // {num}kHz FREQ // {num}{ident} DIMEN // {num}% PERCENTAGE // {num} NUMBER boolean isNum; if ('.' == ch) { int numEnd = parseInt(cp, end); isNum = numEnd >= 0; if (isNum) { end = numEnd; } } else { isNum = true; end = parseNum(cp, start); } if (isNum) { identEnd = parseIdent(cp, end); if (identEnd >= 0) { end = identEnd; } else if (end < limit && '%' == buf[end]) { ++end; } type = CssTokenType.QUANTITY; } else { // lone . // . *yytext type = CssTokenType.PUNCTUATION; } } else if ((identEnd = parseIdent(cp, start)) >= 0) { end = identEnd; if (end - start == 1 && 'U' == ch && end < limit && '+' == buf[end]) { // U\+{range} UNICODERANGE // U\+{h}{1,6}-{h}{1,6} UNICODERANGE // range \?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}\ // (\?{0,3}|{h}(\?{0,2}|{h}(\??|{h}))))) type = CssTokenType.UNICODE_RANGE; ++end; end = parseRange(cp, end); } else if (end < limit && '(' == buf[end]) { ++end; if (end - start == 4 && parseMatch(cp, start, "url(") >= 0) { // "url("{w}{string}{w}")" URI // "url("{w}{url}{w}")" URI end = parseWhitespace(buf, end, limit); int stringEnd = parseString(cp, end); int uriEnd = stringEnd < 0 ? parseUri(cp, end) : -1; if (stringEnd < 0 && uriEnd < 0) { throw new ParseException( new Message( MessageType.EXPECTED_TOKEN, cp.filePositionForOffsets(end, end), MessagePart.Factory.valueOf("{url}"), toMessagePart(cp, end))); } end = stringEnd >= 0 ? stringEnd : uriEnd; end = parseWhitespace(buf, end, limit); if (end == limit || ')' != buf[end]) { throw new ParseException( new Message( MessageType.EXPECTED_TOKEN, cp.filePositionForOffsets(end, end), MessagePart.Factory.valueOf(")"), toMessagePart(cp, end))); } ++end; type = CssTokenType.URI; } else { // {ident}"(" FUNCTION type = CssTokenType.FUNCTION; } } else { // {ident} IDENT type = CssTokenType.IDENT; } } else if (ch == '$' && allowSubstitutions) { // ${<javascript tokens>} if (end < limit && buf[end] != '{') { type = CssTokenType.PUNCTUATION; } else { // 0 - non string // 1 - quoted string // 2 - saw \ in string // 3 - saw close paren int state = 0; // number of parenthetical blocks entered and not exited int nOpen = 0; char delim = 0; do { if (end == limit) { break; } ch = buf[end]; switch (state) { case 0: if (ch == '"' || ch == '\'') { delim = ch; state = 1; } else if (ch == '{') { ++nOpen; } else if (ch == '}') { if (--nOpen == 0) { state = 3; } } break; case 1: if (ch == delim) { state = 0; } else if (ch == '\\') { state = 2; } break; case 2: state = 1; break; } ++end; } while (state != 3); if (state != 3) { throw new ParseException( new Message( MessageType.UNTERMINATED_STRING_TOKEN, cp.filePositionForOffsets(start, end))); } identEnd = parseIdent(cp, end); if (identEnd >= 0) { end = identEnd; } else if (end != limit && '%' == buf[end]) { ++end; } type = CssTokenType.SUBSTITUTION; } } else { // . *yytext type = CssTokenType.PUNCTUATION; } assert end > start; pending = Token.instance(cp.toString(start, end), type, cp.filePositionForOffsets(start, end)); cp.consumeTo(end); }
private static MessagePart toMessagePart(CharProducer cp, int offset) { return MessagePart.Factory.valueOf( offset == cp.getLimit() ? "<end-of-input>" : "" + cp.getBuffer()[offset]); }