2  * This program and the accompanying materials
 
   3  * are made available under the terms of the Common Public License v1.0
 
   4  * which accompanies this distribution, and is available at
 
   5  * http://www.eclipse.org/legal/cpl-v10.html
 
   6  * Created on 05.03.2003
 
   8  * @author Stefan Langer (musk)
 
   9  * @version $Revision: 1.20 $
 
  11 package net.sourceforge.phpeclipse.phpeditor.php;
 
  13 import java.util.ArrayList;
 
  14 import java.util.HashMap;
 
  17 import org.eclipse.jface.text.Assert;
 
  18 import org.eclipse.jface.text.BadLocationException;
 
  19 import org.eclipse.jface.text.IDocument;
 
  20 import org.eclipse.jface.text.ITypedRegion;
 
  21 import org.eclipse.jface.text.rules.ICharacterScanner;
 
  22 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
 
  23 import org.eclipse.jface.text.rules.IToken;
 
  24 import org.eclipse.jface.text.rules.Token;
 
  29 public class PHPPartitionScanner implements IPartitionTokenScanner {
 
  30   private static final boolean DEBUG = false;
 
  32   private boolean fInString = false;
 
  33   private boolean fInDoubString = false;
 
  34   private IDocument fDocument = null;
 
  35   private int fOffset = -1;
 
  36   private String fContentType = IPHPPartitionScannerConstants.HTML;
 
  37   private String fPrevContentType = IPHPPartitionScannerConstants.HTML;
 
  38   private boolean partitionBorder = false;
 
  39   private int fTokenOffset;
 
  40   private int fEnd = -1;
 
  42   private int fCurrentLength;
 
  43   private int fFileType;
 
  44   private Map tokens = new HashMap();
 
  46   public PHPPartitionScanner(int fileType) {
 
  47     this.tokens.put(IPHPPartitionScannerConstants.PHP, new Token(IPHPPartitionScannerConstants.PHP));
 
  49       IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT,
 
  50       new Token(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT));
 
  51     this.tokens.put(IPHPPartitionScannerConstants.HTML, new Token(IPHPPartitionScannerConstants.HTML));
 
  53       IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT,
 
  54       new Token(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT));
 
  56     this.tokens.put(IPHPPartitionScannerConstants.SMARTY, new Token(IPHPPartitionScannerConstants.SMARTY));
 
  58       IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT,
 
  59       new Token(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT));
 
  61     this.tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(IDocument.DEFAULT_CONTENT_TYPE));
 
  65   private IToken getToken(String type) {
 
  66     fLength = fCurrentLength;
 
  71           int line = fDocument.getLineOfOffset(fOffset);
 
  72           System.err.println("Error at " + line + " offset:" + String.valueOf(fOffset - fDocument.getLineOffset(line)));
 
  74       } catch (BadLocationException e) { // should never happen
 
  75         // TODO Write stacktrace to log
 
  79     Assert.isTrue(fLength > 0, "Partition length <= 0!");
 
  81     // String can never cross partition borders so reset string detection
 
  83     fInDoubString = false;
 
  84     IToken token = (IToken) this.tokens.get(type);
 
  85     Assert.isNotNull(token, "Token for type \"" + type + "\" not found!");
 
  87       System.out.println("Partition: fTokenOffset=" + fTokenOffset + " fContentType=" + type + " fLength=" + fLength);
 
  93    * @see org.eclipse.jface.text.rules.IPartitionTokenScanner#setPartialRange(org.eclipse.jface.text.IDocument, int, int, java.lang.String, int)
 
  95   public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
 
  97       System.out.println("*****");
 
  98       System.out.println("PartialRange: contentType=" + contentType + " partitionOffset=" + partitionOffset);
 
 102       if (partitionOffset > -1) {
 
 103         partitionBorder = false;
 
 104         // because of strings we have to parse the whole partition
 
 105         this.setRange(document, partitionOffset, offset - partitionOffset + length);
 
 106         // sometimes we get a wrong partition so we retrieve the partition
 
 107         // directly from the document
 
 108         fContentType = fDocument.getContentType(partitionOffset);
 
 110         this.setRange(document, offset, length);
 
 112     } catch (BadLocationException e) {
 
 113       // should never happen
 
 114       // TODO print stack trace to log
 
 115       // fall back just scan the whole document again
 
 116       this.setRange(document, 0, fDocument.getLength());
 
 122    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
 
 124   public int getTokenLength() {
 
 129    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
 
 131   public int getTokenOffset() {
 
 136    * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
 
 138   public IToken nextToken() {
 
 141     // check if we are not allready at the end of the
 
 143     if ((c = read()) == ICharacterScanner.EOF) {
 
 144       partitionBorder = false;
 
 149     if (partitionBorder) {
 
 150       fTokenOffset = fOffset;
 
 151       partitionBorder = false;
 
 154     while ((c = read()) != ICharacterScanner.EOF) {
 
 157           if (!isInString(IPHPPartitionScannerConstants.PHP)
 
 158             && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT
 
 159             && checkPattern(new char[] { '?', 'p', 'h', 'p' }, true)) {
 
 160             if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 5) {
 
 162               IToken token = getToken(fContentType);
 
 163               // save previouse contenttype
 
 164               //TODO build stack for previouse contenttype 
 
 165               fPrevContentType = fContentType;
 
 167               fContentType = IPHPPartitionScannerConstants.PHP;
 
 171               fContentType = IPHPPartitionScannerConstants.PHP;
 
 173             // remember offset of this partition
 
 174             fTokenOffset = fOffset - 5;
 
 177             !isInString(IPHPPartitionScannerConstants.PHP)
 
 178               && fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT
 
 179               && checkPattern(new char[] { '?' }, false)) {
 
 180             if (fContentType != IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) {
 
 182               IToken token = getToken(fContentType);
 
 183               // save previouse contenttype
 
 184               fPrevContentType = fContentType;
 
 185               fContentType = IPHPPartitionScannerConstants.PHP;
 
 188               fContentType = IPHPPartitionScannerConstants.PHP;
 
 189             // remember offset of this partition
 
 190             fTokenOffset = fOffset - 2;
 
 193             !isInString(IPHPPartitionScannerConstants.PHP)
 
 194               && (fContentType != IPHPPartitionScannerConstants.PHP) // BUG #769044
 
 195               && (fContentType != IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) // BUG #769044
 
 196               && checkPattern(new char[] { '!', '-', '-' })) { // return previouse partition
 
 197             if (fContentType != IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT && fCurrentLength > 4) {
 
 199               IToken token = getToken(fContentType);
 
 200               fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT;
 
 203               fContentType = IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT;
 
 205             fTokenOffset = fOffset - 4;
 
 210           if (!isInString(IPHPPartitionScannerConstants.PHP) && fContentType == IPHPPartitionScannerConstants.PHP) {
 
 211             if ((c = read()) == '>') {
 
 212               if (fPrevContentType != null)
 
 213                 fContentType = fPrevContentType;
 
 215                 fContentType = IPHPPartitionScannerConstants.HTML;
 
 216               partitionBorder = true;
 
 217               return getToken(IPHPPartitionScannerConstants.PHP);
 
 218             } else if (c != ICharacterScanner.EOF)
 
 223           if (!isInString(IPHPPartitionScannerConstants.PHP)
 
 224             && fContentType == IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT
 
 225             && checkPattern(new char[] { '-', '>' })) {
 
 226             fContentType = IPHPPartitionScannerConstants.HTML;
 
 227             partitionBorder = true;
 
 228             return getToken(IPHPPartitionScannerConstants.HTML_MULTILINE_COMMENT);
 
 231         case '{' : // SMARTY code starts here ?
 
 232           if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE) {
 
 233             if ((c = read()) == '*') {
 
 236                   "SMARTYDOC_TOKEN start "
 
 247               if (fContentType != IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT && fCurrentLength > 2) {
 
 248                 // SMARTY doc code starts here 
 
 250                 IToken token = getToken(fContentType);
 
 251                 fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
 
 253                 //              } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset == 2) {
 
 254                 //                fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
 
 255               } else { // if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
 
 256                 fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
 
 257                 fTokenOffset = fOffset - 2;
 
 264                 "SMARTY_TOKEN start "
 
 274             if (fContentType != IPHPPartitionScannerConstants.SMARTY && fCurrentLength > 1) {
 
 276               IToken token = getToken(fContentType);
 
 277               fContentType = IPHPPartitionScannerConstants.SMARTY;
 
 279               //            } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset==1) {
 
 280               //              fContentType = IPHPPartitionScannerConstants.SMARTY;
 
 282               fContentType = IPHPPartitionScannerConstants.SMARTY;
 
 283               fTokenOffset = fOffset - 1;
 
 288         case '}' : // SMARTY code ends here ?
 
 289           if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && fContentType == IPHPPartitionScannerConstants.SMARTY) {
 
 301             fContentType = IPHPPartitionScannerConstants.HTML;
 
 302             partitionBorder = true;
 
 303             return getToken(IPHPPartitionScannerConstants.SMARTY);
 
 307           if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '*') { // MULTINE COMMENT JAVASCRIPT, CSS, PHP
 
 308             if (fContentType == IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) {
 
 310               IToken token = getToken(fContentType);
 
 311               fContentType = IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT;
 
 313             } else if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
 
 314               fTokenOffset = fOffset - 2;
 
 318           } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF)
 
 322           if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '/') {
 
 323             if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
 
 324               fContentType = IPHPPartitionScannerConstants.PHP;
 
 325               partitionBorder = true;
 
 326               return getToken(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT);
 
 327             } else if (fContentType == IPHPPartitionScannerConstants.CSS_MULTILINE_COMMENT) {
 
 328             } else if (fContentType == IPHPPartitionScannerConstants.JS_MULTILINE_COMMENT) {
 
 330           } else if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && (c = read()) == '}') {
 
 333                 "SMARTYDOC_TOKEN end "
 
 342             if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
 
 343               fContentType = IPHPPartitionScannerConstants.HTML;
 
 344               partitionBorder = true;
 
 345               return getToken(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT);
 
 347           } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF) {
 
 353             fInString = !fInString;
 
 356           // toggle String mode
 
 358             fInDoubString = !fInDoubString;
 
 361     } // end of file reached but we have to return the
 
 363     return getToken(fContentType);
 
 366    * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
 
 368   public void setRange(IDocument document, int offset, int length) {
 
 370       System.out.println("SET RANGE: offset=" + offset + " length=" + length);
 
 373     fDocument = document;
 
 375     fTokenOffset = offset;
 
 378     fEnd = fOffset + length;
 
 380     fInDoubString = false;
 
 381     fContentType = IPHPPartitionScannerConstants.HTML;
 
 382     //        String[] prev = getPartitionStack(offset);
 
 387       if (fOffset < fEnd) {
 
 389         return fDocument.getChar(fOffset++);
 
 391       return ICharacterScanner.EOF;
 
 392     } catch (BadLocationException e) {
 
 393       // should never happen
 
 394       // TODO write stacktrace to log
 
 396       return ICharacterScanner.EOF;
 
 400   private void unread() {
 
 405   private void unread(int num) {
 
 407     fCurrentLength -= num;
 
 410   private boolean checkPattern(char[] pattern) {
 
 411     return checkPattern(pattern, false);
 
 415    * Check if next character sequence read from document is equals to 
 
 416    * the provided pattern. Pattern is read from left to right until the 
 
 417    * first character read doesn't match. If this happens all read characters are
 
 419    * @param pattern The pattern to check.
 
 420    * @return <code>true</code> if pattern is equals else returns <code>false</code>.
 
 422   private boolean checkPattern(char[] pattern, boolean ignoreCase) {
 
 423     int prevOffset = fOffset;
 
 424     int prevLength = fCurrentLength;
 
 425     for (int i = 0; i < pattern.length; i++) {
 
 428       if (c == ICharacterScanner.EOF || !letterEquals(c, pattern[i], ignoreCase)) {
 
 429         fOffset = prevOffset;
 
 430         fCurrentLength = prevLength;
 
 438   private boolean letterEquals(int test, char letter, boolean ignoreCase) {
 
 441     else if (ignoreCase && Character.isLowerCase(letter) && test == Character.toUpperCase(letter))
 
 443     else if (ignoreCase && Character.isUpperCase(letter) && test == Character.toLowerCase(letter))
 
 450    * Checks wether the offset is in a <code>String</code> and the specified 
 
 451    * contenttype is the current content type.
 
 452    * Strings are delimited, mutual exclusive, by a " or by a '.
 
 454    * @param contentType The contenttype to check.
 
 455    * @return <code>true</code> if the current offset is in a string else 
 
 458   private boolean isInString(String contentType) {
 
 459     if (fContentType == contentType)
 
 460       return (fInString || fInDoubString);
 
 466    * Returns the previouse partition stack for the given offset.
 
 468    * @param offset The offset to return the previouse partitionstack for.
 
 470    * @return The stack as a string array.
 
 472   private String[] getPartitionStack(int offset) {
 
 473     ArrayList types = new ArrayList();
 
 476       ITypedRegion region = fDocument.getPartition(offset);
 
 477       tmpOffset = region.getOffset();
 
 478       while (tmpOffset - 1 > 0) {
 
 479         region = fDocument.getPartition(tmpOffset - 1);
 
 480         tmpOffset = region.getOffset();
 
 481         types.add(0, region.getType());
 
 483     } catch (BadLocationException e) {
 
 489     String[] retVal = new String[types.size()];
 
 491     retVal = (String[]) types.toArray(retVal);