/******************************************************************************* * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Exadel, Inc. and Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.jsf.ui.adopt; import java.util.*; import org.eclipse.jface.text.IDocument; public class JSPTokenizer { static int ROOT = 0; static int TEXT = 1; static int TAG = 2; static int TAG_CLOSING = 3; static int JSP = 4; static int DIRECTIVE = 5; static int COMMENT = 6; static int DOCTYPE = 7; String text; StringBuilder sb = new StringBuilder(); Token root; int selectionStart; int selectionEnd; int start = -1; int end = -1; public Token parse(IDocument document) { selectionStart = 0; selectionEnd = document.getLength(); text = document.get(); root = new Token(ROOT, "", 0, text.length(), null); //$NON-NLS-1$ root.indent = ""; //$NON-NLS-1$ root.indentLevel = -1; tokenize(); return root; } private void tokenize() { int cursor = 0; Token current = root; Token last = null; while(cursor < text.length()) { int p = text.indexOf('<', cursor); if(p < 0) { current.addChild(last = createTag(TEXT, "", cursor, text.length() - cursor, last)); //$NON-NLS-1$ cursor = text.length(); } else { if(p > cursor) { current.addChild(last = createTag(TEXT, "", cursor, p - cursor, last)); //$NON-NLS-1$ cursor = p; } if(isStringStart(cursor, "<!DOCTYPE")) { //$NON-NLS-1$ int l = "<!DOCTYPE".length(); //$NON-NLS-1$ int q = text.indexOf(">", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 1; int k = skipToName(cursor + l, nc); String tag = readTag(cursor + l + k); current.addChild(last = createTag(DOCTYPE, tag, cursor, nc - cursor, last)); int ab = cursor + l + k + tag.length(); int al = nc - ab; last.attributes = getDoctype(text, ab, al); cursor = nc; } else if(isStringStart(cursor, "<%@")) { //$NON-NLS-1$ int q = text.indexOf("%>", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 2; int k = skipToName(cursor + 3, nc); String tag = readTag(cursor + 3 + k); current.addChild(last = createTag(DIRECTIVE, tag, cursor, nc - cursor, last)); int ab = cursor + 3 + k + tag.length(); int al = nc - ab; last.attributes = getAttributes(text, ab, al); cursor = nc; } else if(isStringStart(cursor, "<%")) { //$NON-NLS-1$ int q = text.indexOf("%>", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 2; current.addChild(last = createTag(JSP, "", cursor, nc - cursor, last)); //$NON-NLS-1$ cursor = nc; } else if(isStringStart(cursor, "<!--")) { //$NON-NLS-1$ int q = text.indexOf("-->", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 3; current.addChild(last = createTag(COMMENT, "", cursor, nc - cursor, last)); //$NON-NLS-1$ cursor = nc; } else if(isStringStart(cursor, "<!")) { //$NON-NLS-1$ ///This is doctype int q = text.indexOf(">", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 1; current.addChild(last = createTag(COMMENT, "", cursor, nc - cursor, last)); //$NON-NLS-1$ cursor = nc; } else if(isStringStart(cursor, "</")) { //$NON-NLS-1$ String tag = readTag(cursor + 2); int q = text.indexOf(">", cursor); //$NON-NLS-1$ int nc = (q < 0) ? text.length() : q + 1; last = createTag(TAG_CLOSING, tag, cursor, nc - cursor, last); current = findParent(current, last); current.addChild(last); cursor = nc; } else { String tag = readTag(cursor + 1); int q = findTagClosingSymbol(cursor); int nc = (q < 0) ? text.length() : q + 1; last = createTag(TAG, tag, cursor, nc - cursor, last); int ab = cursor + 1 + tag.length(); int al = nc - ab; last.attributes = getAttributes(text, ab, al); if(isOptionallyClosed(tag)) { current = findParentForOptionallyClosedTag(current, tag); } current.addChild(last); cursor = nc; if(q > 0 && text.charAt(q - 1) != '/' && areChildrenAllowed(tag)) { current = last; } } } } } private Token findParent(Token current, Token t) { Token c = current; while(c.kind != ROOT) { if(c.name.equals(t.name)) return c.parent; c = c.parent; } return current; } private boolean isOptionallyClosed(String name) { return ".body.p.dt.dd.li.ol.option.thead.tfoot.tbody.colgroup.tr.td.th.head.html.".indexOf(name.toLowerCase()) >= 0; //$NON-NLS-1$ } private Token findParentForOptionallyClosedTag(Token current, String name) { String n1 = name.toLowerCase(); String n2 = current.name.toLowerCase(); if("p".equals(n1)) { //$NON-NLS-1$ if(n2.equals("p")) return current.parent; //$NON-NLS-1$ } else if("tr".equals(n1)) { //$NON-NLS-1$ if(n2.equals("tr")) return current.parent; //$NON-NLS-1$ if(n2.equals("th") || n2.equals("td") || n2.equals("p")) return findParentForOptionallyClosedTag(current.parent, name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } else if("td".equals(n1) || "th".equals(n1)) { //$NON-NLS-1$ //$NON-NLS-2$ if(n2.equals("th") || n2.equals("td")) return current.parent; //$NON-NLS-1$ //$NON-NLS-2$ if(n2.equals("p")) return findParentForOptionallyClosedTag(current.parent, name); //$NON-NLS-1$ } return current; } private boolean areChildrenAllowed(String name) { return ".br.area.link.img.param.hr.input.col.isindex.base.meta.".indexOf("." + name.toLowerCase() + ".") < 0; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } private Token createTag(int kind, String name, int off, int length, Token previous) { Token t = new Token(kind, name, off, length, previous); t.indentLength = (kind == TEXT) ? -1 : computeIndentLength(off); if(previous != null && previous.kind == JSPTokenizer.TEXT && t.indentLength > 0) { previous.length -= t.indentLength; t.off -= t.indentLength; t.length += t.indentLength; } return t; } private boolean isStringStart(int c, String s) { if(text.length() <= c + s.length()) return false; for (int i = 0; i < s.length(); i++) { if(text.charAt(c + i) != s.charAt(i)) return false; } return true; } private int skipToName(int b, int e) { int t = b; while(t < e && !isNameChar(text.charAt(t))) { ++t; } return t - b; } private String readTag(int c) { int k = c; while(k < text.length() && isNameChar(text.charAt(k))) ++k; return text.substring(c, k); } private boolean isNameChar(char ch) { return Character.isJavaIdentifierPart(ch) || ch == '-' || ch == ':'; } private int computeIndentLength(int off) { off--; int l = 0; while(off >= 0) { char ch = text.charAt(off); if(ch == '\n' || ch == '\r') return l; if(!Character.isWhitespace(ch)) return -1; ++l; --off; } return (off < 0) ? l : -1; } public int findTagClosingSymbol(int i) { int l = text.length(); char quota = '\0'; while(i < l) { char ch = text.charAt(i); if(quota != '\0') { if(ch == quota) quota = '\0'; } else if(ch == '\'' || ch == '"') { quota = ch; } else if(ch == '>') { return i; } ++i; } return -1; } public Token getTokenAt(int pos) { return getTokenAt(root, pos); } public Token getTokenAt(Token t, int pos) { if(t == null || t.off > pos) return null; if(t.off + t.length > pos) { return (t.firstChild == null || t.firstChild.off > pos) ? t : getTokenAt(t.firstChild, pos); } if(t.nextSibling != null && t.nextSibling.off <= pos) { return getTokenAt(t.nextSibling, pos); } return (t.firstChild == null) ? t : getTokenAt(t.firstChild, pos); } public boolean isInTagAttributeValue(int pos) { Token t = getTokenAt(root, pos); if(t == null || t.kind != TAG) return false; char quote = '\0'; for (int i = root.off; i < text.length() && i < pos; i++) { char ch = text.charAt(i); if(ch == quote) quote = '\0'; else if(ch == '"' || ch == '\'') quote = ch; } return (quote != '\0'); } static Properties getAttributes(String text, int off, int length) { Properties p = new Properties(); int NOTHING = 0; int NAME = 1; int VALUE = 2; int state = 0; char quote = '\0'; StringBuilder name = new StringBuilder(); StringBuilder value = new StringBuilder(); for (int i = 0; i < length; i++) { char ch = text.charAt(i + off); if(state == NOTHING) { if(" \t\r\n".indexOf(ch) >= 0) continue; //$NON-NLS-1$ if("/\\><%".indexOf(ch) >= 0) break; //$NON-NLS-1$ state = NAME; name.append(ch); } else if(state == NAME) { if(ch == '=') { state = VALUE; } else { name.append(ch); } } else if(state == VALUE) { if(ch == quote) { String n = name.toString().trim(); String v = value.toString(); name.setLength(0); value.setLength(0); p.setProperty(n, v); state = NOTHING; quote = '\0'; } else if(quote == '\0' && (ch == '"' || ch == '\'')) { quote = ch; } else if(quote != '\0') { value.append(ch); } } } return p; } static Properties getDoctype(String text, int off, int length) { Properties p = new Properties(); int b = off; int i = text.indexOf("PUBLIC", off); //$NON-NLS-1$ if(i >= 0) { int i1 = text.indexOf('"', i); if(i1 >= 0) { int i2 = text.indexOf('"', i1 + 1); if(i2 >= 0) { p.setProperty("public", text.substring(i1 + 1, i2)); //$NON-NLS-1$ b = i2 + 1; } } } int i1 = text.indexOf('"', b); if(i1 >= 0) { int i2 = text.indexOf('"', i1 + 1); if(i2 >= 0) { p.setProperty("system", text.substring(i1 + 1, i2)); //$NON-NLS-1$ } } return p; } }