/* * 03/09/2013 * * XmlOccurrenceMarker - Marks occurrences of the current token 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; import java.util.Stack; import javax.swing.text.BadLocationException; import javax.swing.text.Element; import org.fife.ui.rtextarea.SmartHighlightPainter; /** * Marks occurrences of the current token for XML. * * @author Robert Futrell * @version 1.0 */ public class XmlOccurrenceMarker implements OccurrenceMarker { private static final char[] CLOSE_TAG_START = { '<', '/' }; private static final char[] TAG_SELF_CLOSE = { '/', '>' }; /** * {@inheritDoc} */ public void markOccurrences(RSyntaxDocument doc, Token t, RSyntaxTextAreaHighlighter h, SmartHighlightPainter p) { char[] lexeme = t.getLexeme().toCharArray(); int tokenOffs = t.getOffset(); Element root = doc.getDefaultRootElement(); int lineCount = root.getElementCount(); int curLine = root.getElementIndex(t.getOffset()); // For now, we only check for tags on the current line, for // simplicity. Tags spanning multiple lines aren't common anyway. boolean found = false; boolean forward = true; t = doc.getTokenListForLine(curLine); while (t!=null && t.isPaintable()) { if (t.getType()==Token.MARKUP_TAG_DELIMITER) { if (t.isSingleChar('<') && t.getOffset()+1==tokenOffs) { found = true; break; } else if (t.is(CLOSE_TAG_START) && t.getOffset()+2==tokenOffs) { found = true; forward = false; break; } } t = t.getNextToken(); } if (!found) { return; } if (forward) { int depth = 0; t = t.getNextToken().getNextToken(); do { while (t!=null && t.isPaintable()) { if (t.getType()==Token.MARKUP_TAG_DELIMITER) { if (t.isSingleChar('<')) { depth++; } else if (t.is(TAG_SELF_CLOSE)) { if (depth>0) { depth--; } else { return; // No match for this tag } } else if (t.is(CLOSE_TAG_START)) { if (depth>0) { depth--; } else { Token match = t.getNextToken(); if (match!=null && match.is(lexeme)) { try { int end = match.getOffset() + match.length(); h.addMarkedOccurrenceHighlight(match.getOffset(), end, p); end = tokenOffs + match.length(); h.addMarkedOccurrenceHighlight(tokenOffs, end, p); } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } } return; // We're done! } } } t = t.getNextToken(); } if (++curLine<lineCount) { t = doc.getTokenListForLine(curLine); } } while (curLine<lineCount); } else { // !forward Stack<Token> matches = new Stack<Token>(); boolean inPossibleMatch = false; t = doc.getTokenListForLine(curLine); final int endBefore = tokenOffs - 2; // Stop before "</". do { while (t!=null && t.getOffset()<endBefore && t.isPaintable()) { if (t.getType()==Token.MARKUP_TAG_DELIMITER) { if (t.isSingleChar('<')) { Token next = t.getNextToken(); if (next!=null) { if (next.is(lexeme)) { matches.push(next); inPossibleMatch = true; } else { inPossibleMatch = false; } t = next; } } else if (t.isSingleChar('>')) { inPossibleMatch = false; } else if (inPossibleMatch && t.is(TAG_SELF_CLOSE)) { matches.pop(); } else if (t.is(CLOSE_TAG_START)) { Token next = t.getNextToken(); if (next!=null) { // Invalid XML might not have a match if (next.is(lexeme) && !matches.isEmpty()) { matches.pop(); } t = next; } } } t = t.getNextToken(); } if (!matches.isEmpty()) { try { Token match = matches.pop(); int end = match.getOffset() + match.length(); h.addMarkedOccurrenceHighlight(match.getOffset(), end, p); end = tokenOffs + match.length(); h.addMarkedOccurrenceHighlight(tokenOffs, end, p); } catch (BadLocationException ble) { ble.printStackTrace(); // Never happens } return; } if (--curLine>=0) { t = doc.getTokenListForLine(curLine); } } while (curLine>=0); } } }