/** * This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * Created on 05.03.2003 * * @author Stefan Langer (musk) * @version $Revision: 1.20 $ */ package net.sourceforge.phpeclipse.phpeditor.php; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.rules.ICharacterScanner; import org.eclipse.jface.text.rules.IPartitionTokenScanner; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.Token; /** * */ public class PHPPartitionScanner implements IPartitionTokenScanner { private static final boolean DEBUG = false; private boolean fInString = false; private boolean fInDoubString = false; private IDocument fDocument = null; private int fOffset = -1; private String fContentType = IPHPPartitionScannerConstants.HTML; private String fPrevContentType = IPHPPartitionScannerConstants.HTML; private boolean partitionBorder = false; private int fTokenOffset; private int fEnd = -1; private int fLength; private int fCurrentLength; private int fFileType; private Map tokens = new HashMap(); public PHPPartitionScanner(int fileType) { this.tokens.put(IPHPPartitionScannerConstants.PHP, new Token(IPHPPartitionScannerConstants.PHP)); this.tokens.put( IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT, new Token(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT)); this.tokens.put(IPHPPartitionScannerConstants.HTML, new Token(IPHPPartitionScannerConstants.HTML)); this.tokens.put( IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT, new Token(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT)); this.tokens.put(IPHPPartitionScannerConstants.SMARTY, new Token(IPHPPartitionScannerConstants.SMARTY)); this.tokens.put( IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT, new Token(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT)); this.tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(IDocument.DEFAULT_CONTENT_TYPE)); fFileType = fileType; } private IToken getToken(String type) { fLength = fCurrentLength; if (DEBUG) { try { if (fLength <= 0) { int line = fDocument.getLineOfOffset(fOffset); System.err.println("Error at " + line + " offset:" + String.valueOf(fOffset - fDocument.getLineOffset(line))); } } catch (BadLocationException e) { // should never happen // TODO Write stacktrace to log e.printStackTrace(); } } Assert.isTrue(fLength > 0, "Partition length <= 0!"); fCurrentLength = 0; // String can never cross partition borders so reset string detection fInString = false; fInDoubString = false; IToken token = (IToken) this.tokens.get(type); Assert.isNotNull(token, "Token for type \"" + type + "\" not found!"); if (DEBUG) { System.out.println("Partition: fTokenOffset=" + fTokenOffset + " fContentType=" + type + " fLength=" + fLength); } return token; } /* (non-Javadoc) * @see org.eclipse.jface.text.rules.IPartitionTokenScanner#setPartialRange(org.eclipse.jface.text.IDocument, int, int, java.lang.String, int) */ public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) { if (DEBUG) { System.out.println("*****"); System.out.println("PartialRange: contentType=" + contentType + " partitionOffset=" + partitionOffset); } try { if (partitionOffset > -1) { partitionBorder = false; // because of strings we have to parse the whole partition this.setRange(document, partitionOffset, offset - partitionOffset + length); // sometimes we get a wrong partition so we retrieve the partition // directly from the document fContentType = fDocument.getContentType(partitionOffset); } else this.setRange(document, offset, length); } catch (BadLocationException e) { // should never happen // TODO print stack trace to log // fall back just scan the whole document again this.setRange(document, 0, fDocument.getLength()); } } /* (non-Javadoc) * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength() */ public int getTokenLength() { return fLength; } /* (non-Javadoc) * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset() */ public int getTokenOffset() { return fTokenOffset; } /* (non-Javadoc) * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken() */ public IToken nextToken() { int c; // check if we are not allready at the end of the // file if ((c = read()) == ICharacterScanner.EOF) { partitionBorder = false; return Token.EOF; } else unread(); if (partitionBorder) { fTokenOffset = fOffset; partitionBorder = false; } while ((c = read()) != ICharacterScanner.EOF) { switch (c) { case '<' : if (!isInString(IPHPPartitionScannerConstants.PHP) && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT && checkPattern(new char[] { '?', 'p', 'h', 'p' }, true)) { if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 5) { unread(5); IToken token = getToken(fContentType); // save previouse contenttype //TODO build stack for previouse contenttype fPrevContentType = fContentType; fContentType = IPHPPartitionScannerConstants.PHP; return token; } else fContentType = IPHPPartitionScannerConstants.PHP; // remember offset of this partition fTokenOffset = fOffset - 5; fCurrentLength = 5; } else if ( !isInString(IPHPPartitionScannerConstants.PHP) && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT && checkPattern(new char[] { '?' }, false)) { if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) { unread(2); IToken token = getToken(fContentType); // save previouse contenttype fPrevContentType = fContentType; fContentType = IPHPPartitionScannerConstants.PHP; return token; } else fContentType = IPHPPartitionScannerConstants.PHP; // remember offset of this partition fTokenOffset = fOffset - 2; fCurrentLength = 2; } else if ( !isInString(IPHPPartitionScannerConstants.PHP) && (fContentType != IPHPPartitionScannerConstants.PHP) // BUG #769044 && (fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) // BUG #769044 && checkPattern(new char[] { '!', '-', '-' })) { // return previouse partition if (fContentType != IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT && fCurrentLength > 4) { unread(4); IToken token = getToken(fContentType); fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT; return token; } else fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT; fTokenOffset = fOffset - 4; fCurrentLength = 4; } break; case '?' : if (!isInString(IPHPPartitionScannerConstants.PHP) && fContentType == IPHPPartitionScannerConstants.PHP) { if ((c = read()) == '>') { if (fPrevContentType != null) fContentType = fPrevContentType; else fContentType = IPHPPartitionScannerConstants.HTML; partitionBorder = true; return getToken(IPHPPartitionScannerConstants.PHP); } else if (c != ICharacterScanner.EOF) unread(); } break; case '-' : if (!isInString(IPHPPartitionScannerConstants.PHP) && fContentType == IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT && checkPattern(new char[] { '-', '>' })) { fContentType = IPHPPartitionScannerConstants.HTML; partitionBorder = true; return getToken(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT); } break; case '{' : // SMARTY code starts here ? if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE) { if ((c = read()) == '*') { if (DEBUG) { System.out.println( "SMARTYDOC_TOKEN start " + fTokenOffset + " fContentType=" + fContentType + " fLength=" + fLength + " fOffset=" + fOffset + " fCurrentLength=" + fCurrentLength); } if (fContentType != IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT && fCurrentLength > 2) { // SMARTY doc code starts here unread(2); IToken token = getToken(fContentType); fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT; return token; // } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset == 2) { // fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT; } else { // if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) { fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT; fTokenOffset = fOffset - 2; fCurrentLength = 2; } break; } if (DEBUG) { System.out.println( "SMARTY_TOKEN start " + fTokenOffset + " fContentType=" + fContentType + " fLength=" + fLength + " fOffset=" + fOffset); } unread(); if (fContentType != IPHPPartitionScannerConstants.SMARTY && fCurrentLength > 1) { unread(1); IToken token = getToken(fContentType); fContentType = IPHPPartitionScannerConstants.SMARTY; return token; // } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset==1) { // fContentType = IPHPPartitionScannerConstants.SMARTY; } else { fContentType = IPHPPartitionScannerConstants.SMARTY; fTokenOffset = fOffset - 1; fCurrentLength = 1; } } break; case '}' : // SMARTY code ends here ? if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && fContentType == IPHPPartitionScannerConstants.SMARTY) { if (DEBUG) { System.out.println( "SMARTY_TOKEN end " + fTokenOffset + " fContentType=" + fContentType + " fLength=" + fLength + " fOffset=" + fOffset); } fContentType = IPHPPartitionScannerConstants.HTML; partitionBorder = true; return getToken(IPHPPartitionScannerConstants.SMARTY); } break; case '/' : if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '*') { // MULTINE COMMENT JAVASCRIPT, CSS, PHP if (fContentType == IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) { unread(2); IToken token = getToken(fContentType); fContentType = IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT; return token; } else if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) { fTokenOffset = fOffset - 2; fCurrentLength = 2; } } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF) unread(); break; case '*' : if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '/') { if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) { fContentType = IPHPPartitionScannerConstants.PHP; partitionBorder = true; return getToken(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT); } else if (fContentType == IPHPPartitionScannerConstants.CSS_MULTILINE_COMMENT) { } else if (fContentType == IPHPPartitionScannerConstants.JS_MULTILINE_COMMENT) { } } else if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && (c = read()) == '}') { if (DEBUG) { System.out.println( "SMARTYDOC_TOKEN end " + fTokenOffset + " fContentType=" + fContentType + " fLength=" + fLength + " fOffset=" + fOffset); } if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) { fContentType = IPHPPartitionScannerConstants.HTML; partitionBorder = true; return getToken(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT); } } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF) { unread(); } break; case '\'' : if (!fInDoubString) fInString = !fInString; break; case '"' : // toggle String mode if (!fInString) fInDoubString = !fInDoubString; break; } } // end of file reached but we have to return the // last partition. return getToken(fContentType); } /* (non-Javadoc) * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int) */ public void setRange(IDocument document, int offset, int length) { if (DEBUG) { System.out.println("SET RANGE: offset=" + offset + " length=" + length); } fDocument = document; fOffset = offset; fTokenOffset = offset; fCurrentLength = 0; fLength = 0; fEnd = fOffset + length; fInString = false; fInDoubString = false; fContentType = IPHPPartitionScannerConstants.HTML; // String[] prev = getPartitionStack(offset); } private int read() { try { if (fOffset < fEnd) { fCurrentLength++; return fDocument.getChar(fOffset++); } return ICharacterScanner.EOF; } catch (BadLocationException e) { // should never happen // TODO write stacktrace to log fOffset = fEnd; return ICharacterScanner.EOF; } } private void unread() { --fOffset; --fCurrentLength; } private void unread(int num) { fOffset -= num; fCurrentLength -= num; } private boolean checkPattern(char[] pattern) { return checkPattern(pattern, false); } /** * Check if next character sequence read from document is equals to * the provided pattern. Pattern is read from left to right until the * first character read doesn't match. If this happens all read characters are * unread. * @param pattern The pattern to check. * @return true if pattern is equals else returns false. */ private boolean checkPattern(char[] pattern, boolean ignoreCase) { int prevOffset = fOffset; int prevLength = fCurrentLength; for (int i = 0; i < pattern.length; i++) { int c = read(); if (c == ICharacterScanner.EOF || !letterEquals(c, pattern[i], ignoreCase)) { fOffset = prevOffset; fCurrentLength = prevLength; return false; } } return true; } private boolean letterEquals(int test, char letter, boolean ignoreCase) { if (test == letter) return true; else if (ignoreCase && Character.isLowerCase(letter) && test == Character.toUpperCase(letter)) return true; else if (ignoreCase && Character.isUpperCase(letter) && test == Character.toLowerCase(letter)) return true; return false; } /** * Checks wether the offset is in a String and the specified * contenttype is the current content type. * Strings are delimited, mutual exclusive, by a " or by a '. * * @param contentType The contenttype to check. * @return true if the current offset is in a string else * returns false. */ private boolean isInString(String contentType) { if (fContentType == contentType) return (fInString || fInDoubString); else return false; } /** * Returns the previouse partition stack for the given offset. * * @param offset The offset to return the previouse partitionstack for. * * @return The stack as a string array. */ private String[] getPartitionStack(int offset) { ArrayList types = new ArrayList(); int tmpOffset = 0; try { ITypedRegion region = fDocument.getPartition(offset); tmpOffset = region.getOffset(); while (tmpOffset - 1 > 0) { region = fDocument.getPartition(tmpOffset - 1); tmpOffset = region.getOffset(); types.add(0, region.getType()); } } catch (BadLocationException e) { if (DEBUG) { e.printStackTrace(); } } String[] retVal = new String[types.size()]; retVal = (String[]) types.toArray(retVal); return retVal; } }