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.21 $
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 "
273 if (c != ICharacterScanner.EOF) {
276 if (fContentType != IPHPPartitionScannerConstants.SMARTY && fCurrentLength > 1) {
278 IToken token = getToken(fContentType);
279 fContentType = IPHPPartitionScannerConstants.SMARTY;
281 // } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset==1) {
282 // fContentType = IPHPPartitionScannerConstants.SMARTY;
284 fContentType = IPHPPartitionScannerConstants.SMARTY;
285 fTokenOffset = fOffset - 1;
290 case '}' : // SMARTY code ends here ?
291 if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && fContentType == IPHPPartitionScannerConstants.SMARTY) {
303 fContentType = IPHPPartitionScannerConstants.HTML;
304 partitionBorder = true;
305 return getToken(IPHPPartitionScannerConstants.SMARTY);
309 if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '*') { // MULTINE COMMENT JAVASCRIPT, CSS, PHP
310 if (fContentType == IPHPPartitionScannerConstants.PHP && fCurrentLength > 2) {
312 IToken token = getToken(fContentType);
313 fContentType = IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT;
315 } else if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
316 fTokenOffset = fOffset - 2;
320 } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF)
324 if (!isInString(IPHPPartitionScannerConstants.PHP) && (c = read()) == '/') {
325 if (fContentType == IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT) {
326 fContentType = IPHPPartitionScannerConstants.PHP;
327 partitionBorder = true;
328 return getToken(IPHPPartitionScannerConstants.PHP_MULTILINE_COMMENT);
329 } else if (fContentType == IPHPPartitionScannerConstants.CSS_MULTILINE_COMMENT) {
330 } else if (fContentType == IPHPPartitionScannerConstants.JS_MULTILINE_COMMENT) {
332 } else if (fFileType == IPHPPartitionScannerConstants.SMARTY_FILE && (c = read()) == '}') {
335 "SMARTYDOC_TOKEN end "
344 if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
345 fContentType = IPHPPartitionScannerConstants.HTML;
346 partitionBorder = true;
347 return getToken(IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT);
349 } else if (!isInString(IPHPPartitionScannerConstants.PHP) && c != ICharacterScanner.EOF) {
355 fInString = !fInString;
358 // toggle String mode
360 fInDoubString = !fInDoubString;
363 } // end of file reached but we have to return the
365 return getToken(fContentType);
368 * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
370 public void setRange(IDocument document, int offset, int length) {
372 System.out.println("SET RANGE: offset=" + offset + " length=" + length);
375 fDocument = document;
377 fTokenOffset = offset;
380 fEnd = fOffset + length;
382 fInDoubString = false;
383 fContentType = IPHPPartitionScannerConstants.HTML;
384 // String[] prev = getPartitionStack(offset);
389 if (fOffset < fEnd) {
391 return fDocument.getChar(fOffset++);
393 return ICharacterScanner.EOF;
394 } catch (BadLocationException e) {
395 // should never happen
396 // TODO write stacktrace to log
398 return ICharacterScanner.EOF;
402 private void unread() {
407 private void unread(int num) {
409 fCurrentLength -= num;
412 private boolean checkPattern(char[] pattern) {
413 return checkPattern(pattern, false);
417 * Check if next character sequence read from document is equals to
418 * the provided pattern. Pattern is read from left to right until the
419 * first character read doesn't match. If this happens all read characters are
421 * @param pattern The pattern to check.
422 * @return <code>true</code> if pattern is equals else returns <code>false</code>.
424 private boolean checkPattern(char[] pattern, boolean ignoreCase) {
425 int prevOffset = fOffset;
426 int prevLength = fCurrentLength;
427 for (int i = 0; i < pattern.length; i++) {
430 if (c == ICharacterScanner.EOF || !letterEquals(c, pattern[i], ignoreCase)) {
431 fOffset = prevOffset;
432 fCurrentLength = prevLength;
440 private boolean letterEquals(int test, char letter, boolean ignoreCase) {
443 else if (ignoreCase && Character.isLowerCase(letter) && test == Character.toUpperCase(letter))
445 else if (ignoreCase && Character.isUpperCase(letter) && test == Character.toLowerCase(letter))
452 * Checks wether the offset is in a <code>String</code> and the specified
453 * contenttype is the current content type.
454 * Strings are delimited, mutual exclusive, by a " or by a '.
456 * @param contentType The contenttype to check.
457 * @return <code>true</code> if the current offset is in a string else
460 private boolean isInString(String contentType) {
461 if (fContentType == contentType)
462 return (fInString || fInDoubString);
468 * Returns the previouse partition stack for the given offset.
470 * @param offset The offset to return the previouse partitionstack for.
472 * @return The stack as a string array.
474 private String[] getPartitionStack(int offset) {
475 ArrayList types = new ArrayList();
478 ITypedRegion region = fDocument.getPartition(offset);
479 tmpOffset = region.getOffset();
480 while (tmpOffset - 1 > 0) {
481 region = fDocument.getPartition(tmpOffset - 1);
482 tmpOffset = region.getOffset();
483 types.add(0, region.getType());
485 } catch (BadLocationException e) {
491 String[] retVal = new String[types.size()];
493 retVal = (String[]) types.toArray(retVal);