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);