
org.fife.ui.rsyntaxtextarea.folding.HtmlFoldParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rsyntaxtextarea Show documentation
Show all versions of rsyntaxtextarea Show documentation
RSyntaxTextArea is the syntax highlighting text editor for Swing applications. Features include syntax highlighting for 40+ languages, code folding, code completion, regex find and replace, macros, code templates, undo/redo, line numbering and bracket matching.
The newest version!
/*
* 09/30/2012
*
* HtmlFoldParser.java - Fold parser for HTML 5 and PHP.
*
* This library is distributed under a modified BSD license. See the included
* LICENSE file for details.
*/
package org.fife.ui.rsyntaxtextarea.folding;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.Token;
import org.fife.ui.rsyntaxtextarea.TokenTypes;
/**
* Fold parser for HTML 5, PHP and JSP. For HTML, we currently don't fold
* everything possible, just the "big" stuff. For PHP, we only fold
* the "big" HTML stuff and PHP regions, not code blocks in the actual PHP.
* For JSP we only fold the "big" HTML stuff and JSP blocks, not anything in
* the actual Java code.
*
* @author Robert Futrell
* @version 1.0
*/
public class HtmlFoldParser implements FoldParser {
/**
* Constant denoting we're folding HTML.
*/
public static final int LANGUAGE_HTML = -1;
/**
* Constant denoting we're folding PHP.
*/
public static final int LANGUAGE_PHP = 0;
/**
* Constant denoting we're folding JSP.
*/
public static final int LANGUAGE_JSP = 1;
/**
* The language we're folding.
*/
private final int language;
/**
* The set of tags we allow to be folded. These are tags that must have
* explicit close tags in both HTML 4 and HTML 5.
*/
private static final Set FOLDABLE_TAGS;
private static final char[] MARKUP_CLOSING_TAG_START = "".toCharArray();
//private static final char[] MARKUP_SHORT_TAG_END = "/>".toCharArray();
private static final char[] MLC_START = "".toCharArray();
private static final char[] PHP_START = "".toCharArray(); // and ".toCharArray();
// Scriptlets, declarations, and expressions all start the same way.
private static final char[] JSP_START = "<%".toCharArray();
private static final char[] JSP_END = "%>".toCharArray();
private static final char[][] LANG_START = { PHP_START, JSP_START };
private static final char[][] LANG_END = { PHP_END, JSP_END };
private static final char[] JSP_COMMENT_START = "<%--".toCharArray();
private static final char[] JSP_COMMENT_END = "--%>".toCharArray();
static {
FOLDABLE_TAGS = new HashSet<>();
FOLDABLE_TAGS.add("body");
FOLDABLE_TAGS.add("canvas");
FOLDABLE_TAGS.add("div");
FOLDABLE_TAGS.add("form");
FOLDABLE_TAGS.add("head");
FOLDABLE_TAGS.add("html");
FOLDABLE_TAGS.add("ol");
FOLDABLE_TAGS.add("pre");
FOLDABLE_TAGS.add("script");
FOLDABLE_TAGS.add("span");
FOLDABLE_TAGS.add("style");
FOLDABLE_TAGS.add("table");
FOLDABLE_TAGS.add("tfoot");
FOLDABLE_TAGS.add("thead");
FOLDABLE_TAGS.add("tr");
FOLDABLE_TAGS.add("td");
FOLDABLE_TAGS.add("ul");
}
/**
* Constructor.
*
* @param language The language to fold, such as {@link #LANGUAGE_PHP}.
*/
public HtmlFoldParser(int language) {
if (languageLANGUAGE_JSP) {
throw new IllegalArgumentException("Invalid language: " + language);
}
this.language = language;
}
@Override
public List getFolds(RSyntaxTextArea textArea) {
List folds = new ArrayList<>();
Stack tagNameStack = new Stack<>();
boolean inSubLanguage = false;
Fold currentFold = null;
int lineCount = textArea.getLineCount();
boolean inMLC = false;
boolean inJSPMLC = false;
TagCloseInfo tci = new TagCloseInfo();
try {
for (int line=0; line=0 && t.getType()==TokenTypes.MARKUP_TAG_DELIMITER) {
// , or %>
else if (t.startsWith(LANG_END[language]) && currentFold != null) {
int phpEnd = t.getEndOffset() - 1;
currentFold.setEndOffset(phpEnd);
Fold parentFold = currentFold.getParent();
// Don't add fold markers for single-line blocks
if (currentFold.isOnSingleLine()) {
removeFold(currentFold, folds);
}
currentFold = parentFold;
inSubLanguage = false;
t = t.getNextToken();
continue;
}
}
if (!inSubLanguage) {
if (t.getType()== TokenTypes.MARKUP_COMMENT) {
// Continuing an MLC from a previous line
if (inMLC) {
// Found the end of the MLC starting on a previous line...
if (t.endsWith(MLC_END)) {
int mlcEnd = t.getEndOffset() - 1;
currentFold.setEndOffset(mlcEnd);
Fold parentFold = currentFold.getParent();
// Don't add fold markers for single-line blocks
if (currentFold.isOnSingleLine()) {
removeFold(currentFold, folds);
}
currentFold = parentFold;
inMLC = false;
}
// Otherwise, this MLC is continuing on to yet
// another line.
}
// Continuing a JS MLC from a previous line
else if (inJSPMLC) {
// Found the end of the MLC starting on a previous line...
if (t.endsWith(JSP_COMMENT_END)) {
int mlcEnd = t.getEndOffset() - 1;
currentFold.setEndOffset(mlcEnd);
Fold parentFold = currentFold.getParent();
// Don't add fold markers for single-line blocks
if (currentFold.isOnSingleLine()) {
removeFold(currentFold, folds);
}
currentFold = parentFold;
inJSPMLC = false;
}
// Otherwise, this MLC is continuing on to yet
// another line.
}
// Starting an MLC that ends on a later line...
else if (t.startsWith(MLC_START) && !t.endsWith(MLC_END)) {
if (currentFold==null) {
currentFold = new Fold(FoldType.COMMENT, textArea, t.getOffset());
folds.add(currentFold);
}
else {
currentFold = currentFold.createChild(FoldType.COMMENT, t.getOffset());
}
inMLC = true;
}
// Starting a JSP comment that ends on a later line...
else if (language==LANGUAGE_JSP &&
t.startsWith(JSP_COMMENT_START) &&
!t.endsWith(JSP_COMMENT_END)) {
if (currentFold==null) {
currentFold = new Fold(FoldType.COMMENT, textArea, t.getOffset());
folds.add(currentFold);
}
else {
currentFold = currentFold.createChild(FoldType.COMMENT, t.getOffset());
}
inJSPMLC = true;
}
}
// If we're starting a new tag...
else if (t.isSingleChar(TokenTypes.MARKUP_TAG_DELIMITER, '<')) {
Token tagStartToken = t;
Token tagNameToken = t.getNextToken();
if (isFoldableTag(tagNameToken)) {
int newLine = getTagCloseInfo(tagNameToken, textArea, line, tci);
if (tci.line==-1) { // EOF reached before end of tag
return folds;
}
// We have found either ">" or "/>" with tci.
//System.out.println(line + ", "+ tci + ", " + t);
Token tagCloseToken = tci.closeToken;
if (tagCloseToken.isSingleChar(TokenTypes.MARKUP_TAG_DELIMITER, '>')) {
if (currentFold==null) {
currentFold = new Fold(FoldType.CODE, textArea,
tagStartToken.getOffset());
folds.add(currentFold);
}
else {
currentFold = currentFold.createChild(FoldType.CODE,
tagStartToken.getOffset());
}
tagNameStack.push(tagNameToken.getLexeme());
}
t = tagCloseToken; // Continue parsing after tag
line = newLine;
}
}
// If we've found a closing tag (e.g. "
>
" or "/>
"). This should only be
* called after a tag name has been parsed, to ensure the "closing" of
* other tags is not identified.
*
* @param tagNameToken The token denoting the name of the tag.
* @param textArea The text area whose contents are being parsed.
* @param line The line we're currently on.
* @param info On return, information about the closing of the tag is
* returned in this object.
* @return The line number of the closing tag token.
*/
private int getTagCloseInfo(Token tagNameToken, RSyntaxTextArea textArea,
int line, TagCloseInfo info) {
info.reset();
Token t = tagNameToken.getNextToken();
do {
while (t!=null && t.getType()!=TokenTypes.MARKUP_TAG_DELIMITER) {
t = t.getNextToken();
}
if (t!=null) {
info.closeToken = t;
info.line = line;
break;
}
} while (++linenull
.
* @return Whether this tag can be a foldable region.
*/
private static boolean isFoldableTag(Token tagNameToken) {
return tagNameToken!=null && tagNameToken.getLexeme() != null &&
FOLDABLE_TAGS.contains(tagNameToken.getLexeme().toLowerCase());
}
/**
* If this fold has a parent fold, this method removes it from its parent.
* Otherwise, it's assumed to be the most recent (top-level) fold in the
* folds
list, and is removed from that.
*
* @param fold The fold to remove.
* @param folds The list of top-level folds.
*/
private static void removeFold(Fold fold, List>
" or "/>
").
*/
private static final class TagCloseInfo {
private Token closeToken;
private int line;
void reset() {
closeToken = null;
line = -1;
}
@Override
public String toString() {
return "[TagCloseInfo: " +
"closeToken=" + closeToken +
", line=" + line +
"]";
}
}
}