/* * 10/23/2011 * * XmlFoldParser.java - Fold parser for XML. * * This library is distributed under a modified BSD license. See the included * RSyntaxTextArea.License.txt file for details. */ package org.fife.ui.rsyntaxtextarea.folding; import java.util.ArrayList; import java.util.List; import javax.swing.text.BadLocationException; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Token; /** * Fold parser for XML. Any tags that span more than one line, as well as * comment regions spanning more than one line, are identified as foldable * regions. * * @author Robert Futrell * @version 1.0 */ public class XmlFoldParser implements FoldParser { private static final char[] MARKUP_CLOSING_TAG_START = { '<', '/' }; private static final char[] MARKUP_SHORT_TAG_END = { '/', '>' }; private static final char[] MLC_END = { '-', '-', '>' }; /** * {@inheritDoc} */ public List<Fold> getFolds(RSyntaxTextArea textArea) { List<Fold> folds = new ArrayList<Fold>(); Fold currentFold = null; int lineCount = textArea.getLineCount(); boolean inMLC = false; int mlcStart = 0; try { for (int line=0; line<lineCount; line++) { Token t = textArea.getTokenListForLine(line); while (t!=null && t.isPaintable()) { if (t.isComment()) { // 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; if (currentFold==null) { currentFold = new Fold(FoldType.COMMENT, textArea, mlcStart); currentFold.setEndOffset(mlcEnd); folds.add(currentFold); currentFold = null; } else { currentFold = currentFold.createChild(FoldType.COMMENT, mlcStart); currentFold.setEndOffset(mlcEnd); currentFold = currentFold.getParent(); } inMLC = false; mlcStart = 0; } // Otherwise, this MLC is continuing on to yet // another line. } else { // If we're an MLC that ends on a later line... if (t.getType()==Token.COMMENT_MULTILINE && !t.endsWith(MLC_END)) { inMLC = true; mlcStart = t.getOffset(); } } } else if (t.isSingleChar(Token.MARKUP_TAG_DELIMITER, '<')) { if (currentFold==null) { currentFold = new Fold(FoldType.CODE, textArea, t.getOffset()); folds.add(currentFold); } else { currentFold = currentFold.createChild(FoldType.CODE, t.getOffset()); } } else if (t.is(Token.MARKUP_TAG_DELIMITER, MARKUP_SHORT_TAG_END)) { if (currentFold!=null) { Fold parentFold = currentFold.getParent(); removeFold(currentFold, folds); currentFold = parentFold; } } else if (t.is(Token.MARKUP_TAG_DELIMITER, MARKUP_CLOSING_TAG_START)) { if (currentFold!=null) { currentFold.setEndOffset(t.getOffset()); Fold parentFold = currentFold.getParent(); // Don't add fold markers for single-line blocks if (currentFold.isOnSingleLine()) { removeFold(currentFold, folds); } currentFold = parentFold; } } t = t.getNextToken(); } } } catch (BadLocationException ble) { // Should never happen ble.printStackTrace(); } return folds; } /** * 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 * <code>folds</code> list, and is removed from that. * * @param fold The fold to remove. * @param folds The list of top-level folds. */ private static final void removeFold(Fold fold, List<Fold> folds) { if (!fold.removeFromParent()) { folds.remove(folds.size()-1); } } }