35d520896d1fd27756f21931a7f6c9b9ea6f72c1
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPPartitionScanner.java
1 /**
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
7  *
8  * @author Stefan Langer (musk)
9  * @version $Revision: 1.23 $
10  */
11 package net.sourceforge.phpeclipse.phpeditor.php;
12
13 import java.util.ArrayList;
14 import java.util.HashMap;
15 import java.util.Map;
16
17 import net.sourceforge.phpdt.internal.ui.text.*;
18
19 import org.eclipse.jface.text.Assert;
20 import org.eclipse.jface.text.BadLocationException;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.ITypedRegion;
23 import org.eclipse.jface.text.rules.ICharacterScanner;
24 import org.eclipse.jface.text.rules.IPartitionTokenScanner;
25 import org.eclipse.jface.text.rules.IToken;
26 import org.eclipse.jface.text.rules.Token;
27
28 /**
29  * 
30  */
31 public class PHPPartitionScanner implements IPartitionTokenScanner {
32   private static final boolean DEBUG = false;
33
34   private boolean fInString = false;
35   private boolean fInDoubString = false;
36   private IDocument fDocument = null;
37   private int fOffset = -1;
38   private String fContentType = IPHPPartitions.HTML;
39   private String fPrevContentType = IPHPPartitions.HTML;
40   private boolean partitionBorder = false;
41   private int fTokenOffset;
42   private int fEnd = -1;
43   private int fLength;
44   private int fCurrentLength;
45   private int fFileType;
46   private Map tokens = new HashMap();
47
48   public PHPPartitionScanner() {
49         this(IPHPPartitions.PHP_FILE);
50   }
51   
52   public PHPPartitionScanner(int fileType) {
53     this.tokens.put(IPHPPartitions.PHP_PARTITIONING, new Token(IPHPPartitions.PHP_PARTITIONING));
54     this.tokens.put(
55       IPHPPartitions.PHP_MULTILINE_COMMENT,
56       new Token(IPHPPartitions.PHP_MULTILINE_COMMENT));
57     this.tokens.put(IPHPPartitions.HTML, new Token(IPHPPartitions.HTML));
58     this.tokens.put(
59       IPHPPartitions.HTML_MULTILINE_COMMENT,
60       new Token(IPHPPartitions.HTML_MULTILINE_COMMENT));
61
62     this.tokens.put(IPHPPartitions.SMARTY, new Token(IPHPPartitions.SMARTY));
63     this.tokens.put(
64       IPHPPartitions.SMARTY_MULTILINE_COMMENT,
65       new Token(IPHPPartitions.SMARTY_MULTILINE_COMMENT));
66
67     this.tokens.put(IDocument.DEFAULT_CONTENT_TYPE, new Token(IDocument.DEFAULT_CONTENT_TYPE));
68     fFileType = fileType;
69   }
70
71   private IToken getToken(String type) {
72     fLength = fCurrentLength;
73     if (DEBUG) {
74
75       try {
76         if (fLength <= 0) {
77           int line = fDocument.getLineOfOffset(fOffset);
78           System.err.println("Error at " + line + " offset:" + String.valueOf(fOffset - fDocument.getLineOffset(line)));
79         }
80       } catch (BadLocationException e) { // should never happen
81         // TODO Write stacktrace to log
82         e.printStackTrace();
83       }
84     }
85     Assert.isTrue(fLength > 0, "Partition length <= 0!");
86     fCurrentLength = 0;
87     // String can never cross partition borders so reset string detection
88     fInString = false;
89     fInDoubString = false;
90     IToken token = (IToken) this.tokens.get(type);
91     Assert.isNotNull(token, "Token for type \"" + type + "\" not found!");
92     if (DEBUG) {
93       System.out.println("Partition: fTokenOffset=" + fTokenOffset + " fContentType=" + type + " fLength=" + fLength);
94     }
95     return token;
96   }
97
98   /* (non-Javadoc)
99    * @see org.eclipse.jface.text.rules.IPartitionTokenScanner#setPartialRange(org.eclipse.jface.text.IDocument, int, int, java.lang.String, int)
100    */
101   public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
102     if (DEBUG) {
103       System.out.println("*****");
104       System.out.println("PartialRange: contentType=" + contentType + " partitionOffset=" + partitionOffset);
105     }
106
107     try {
108       if (partitionOffset > -1) {
109         partitionBorder = false;
110         // because of strings we have to parse the whole partition
111         this.setRange(document, partitionOffset, offset - partitionOffset + length);
112         // sometimes we get a wrong partition so we retrieve the partition
113         // directly from the document
114         fContentType = fDocument.getContentType(partitionOffset);
115       } else
116         this.setRange(document, offset, length);
117
118     } catch (BadLocationException e) {
119       // should never happen
120       // TODO print stack trace to log
121       // fall back just scan the whole document again
122       this.setRange(document, 0, fDocument.getLength());
123     }
124
125   }
126
127   /* (non-Javadoc)
128    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
129    */
130   public int getTokenLength() {
131     return fLength;
132   }
133
134   /* (non-Javadoc)
135    * @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
136    */
137   public int getTokenOffset() {
138     return fTokenOffset;
139   }
140
141   /* (non-Javadoc)
142    * @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
143    */
144   public IToken nextToken() {
145     int c;
146
147     // check if we are not allready at the end of the
148     // file
149     if ((c = read()) == ICharacterScanner.EOF) {
150       partitionBorder = false;
151       return Token.EOF;
152     } else
153       unread();
154
155     if (partitionBorder) {
156       fTokenOffset = fOffset;
157       partitionBorder = false;
158     }
159
160     while ((c = read()) != ICharacterScanner.EOF) {
161       switch (c) {
162         case '<' :
163           if (!isInString(IPHPPartitions.PHP_PARTITIONING)
164             && fContentType != IPHPPartitions.PHP_MULTILINE_COMMENT
165             && checkPattern(new char[] { '?', 'p', 'h', 'p' }, true)) {
166             if (fContentType != IPHPPartitions.PHP_PARTITIONING && fCurrentLength > 5) {
167               unread(5);
168               IToken token = getToken(fContentType);
169               // save previouse contenttype
170               //TODO build stack for previouse contenttype 
171               fPrevContentType = fContentType;
172
173               fContentType = IPHPPartitions.PHP_PARTITIONING;
174
175               return token;
176             } else
177               fContentType = IPHPPartitions.PHP_PARTITIONING;
178
179             // remember offset of this partition
180             fTokenOffset = fOffset - 5;
181             fCurrentLength = 5;
182           } else if (
183             !isInString(IPHPPartitions.PHP_PARTITIONING)
184               && fContentType != IPHPPartitions.PHP_MULTILINE_COMMENT
185               && checkPattern(new char[] { '?' }, false)) {
186             if (fContentType != IPHPPartitions.PHP_PARTITIONING && fCurrentLength > 2) {
187               unread(2);
188               IToken token = getToken(fContentType);
189               // save previouse contenttype
190               fPrevContentType = fContentType;
191               fContentType = IPHPPartitions.PHP_PARTITIONING;
192               return token;
193             } else
194               fContentType = IPHPPartitions.PHP_PARTITIONING;
195             // remember offset of this partition
196             fTokenOffset = fOffset - 2;
197             fCurrentLength = 2;
198           } else if (
199             !isInString(IPHPPartitions.PHP_PARTITIONING)
200               && (fContentType != IPHPPartitions.PHP_PARTITIONING) // BUG #769044
201               && (fContentType != IPHPPartitions.PHP_MULTILINE_COMMENT) // BUG #769044
202               && checkPattern(new char[] { '!', '-', '-' })) { // return previouse partition
203             if (fContentType != IPHPPartitions.HTML_MULTILINE_COMMENT && fCurrentLength > 4) {
204               unread(4);
205               IToken token = getToken(fContentType);
206               fContentType = IPHPPartitions.HTML_MULTILINE_COMMENT;
207               return token;
208             } else
209               fContentType = IPHPPartitions.HTML_MULTILINE_COMMENT;
210
211             fTokenOffset = fOffset - 4;
212             fCurrentLength = 4;
213           }
214           break;
215         case '?' :
216           if (!isInString(IPHPPartitions.PHP_PARTITIONING) && fContentType == IPHPPartitions.PHP_PARTITIONING) {
217             if ((c = read()) == '>') {
218               if (fPrevContentType != null)
219                 fContentType = fPrevContentType;
220               else
221                 fContentType = IPHPPartitions.HTML;
222               partitionBorder = true;
223               return getToken(IPHPPartitions.PHP_PARTITIONING);
224             } else if (c != ICharacterScanner.EOF)
225               unread();
226           }
227           break;
228         case '-' :
229           if (!isInString(IPHPPartitions.PHP_PARTITIONING)
230             && fContentType == IPHPPartitions.HTML_MULTILINE_COMMENT
231             && checkPattern(new char[] { '-', '>' })) {
232             fContentType = IPHPPartitions.HTML;
233             partitionBorder = true;
234             return getToken(IPHPPartitions.HTML_MULTILINE_COMMENT);
235           }
236           break;
237         case '{' : // SMARTY code starts here ?
238           if (fFileType == IPHPPartitions.SMARTY_FILE) {
239             if ((c = read()) == '*') {
240               if (DEBUG) {
241                 System.out.println(
242                   "SMARTYDOC_TOKEN start "
243                     + fTokenOffset
244                     + " fContentType="
245                     + fContentType
246                     + " fLength="
247                     + fLength
248                     + " fOffset="
249                     + fOffset
250                     + " fCurrentLength="
251                     + fCurrentLength);
252               }
253               if (fContentType != IPHPPartitions.SMARTY_MULTILINE_COMMENT && fCurrentLength > 2) {
254                 // SMARTY doc code starts here 
255                 unread(2);
256                 IToken token = getToken(fContentType);
257                 fContentType = IPHPPartitions.SMARTY_MULTILINE_COMMENT;
258                 return token;
259                 //              } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset == 2) {
260                 //                fContentType = IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT;
261               } else { // if (fContentType == IPHPPartitionScannerConstants.SMARTY_MULTILINE_COMMENT) {
262                 fContentType = IPHPPartitions.SMARTY_MULTILINE_COMMENT;
263                 fTokenOffset = fOffset - 2;
264                 fCurrentLength = 2;
265               }
266               break;
267             }
268             if (DEBUG) {
269               System.out.println(
270                 "SMARTY_TOKEN start "
271                   + fTokenOffset
272                   + " fContentType="
273                   + fContentType
274                   + " fLength="
275                   + fLength
276                   + " fOffset="
277                   + fOffset);
278             }
279             if (c != ICharacterScanner.EOF) {
280                 unread();
281             }
282             if (fContentType != IPHPPartitions.SMARTY && fCurrentLength > 1) {
283               unread(1);
284               IToken token = getToken(fContentType);
285               fContentType = IPHPPartitions.SMARTY;
286               return token;
287               //            } else if (fContentType == IPHPPartitionScannerConstants.HTML && fOffset==1) {
288               //              fContentType = IPHPPartitionScannerConstants.SMARTY;
289             } else {
290               fContentType = IPHPPartitions.SMARTY;
291               fTokenOffset = fOffset - 1;
292               fCurrentLength = 1;
293             }
294           }
295           break;
296         case '}' : // SMARTY code ends here ?
297           if (fFileType == IPHPPartitions.SMARTY_FILE && fContentType == IPHPPartitions.SMARTY) {
298             if (DEBUG) {
299               System.out.println(
300                 "SMARTY_TOKEN end "
301                   + fTokenOffset
302                   + " fContentType="
303                   + fContentType
304                   + " fLength="
305                   + fLength
306                   + " fOffset="
307                   + fOffset);
308             }
309             fContentType = IPHPPartitions.HTML;
310             partitionBorder = true;
311             return getToken(IPHPPartitions.SMARTY);
312           }
313           break;
314         case '/' :
315           if (!isInString(IPHPPartitions.PHP_PARTITIONING) && (c = read()) == '*') { // MULTINE COMMENT JAVASCRIPT, CSS, PHP
316             if (fContentType == IPHPPartitions.PHP_PARTITIONING && fCurrentLength > 2) {
317               unread(2);
318               IToken token = getToken(fContentType);
319               fContentType = IPHPPartitions.PHP_MULTILINE_COMMENT;
320               return token;
321             } else if (fContentType == IPHPPartitions.PHP_MULTILINE_COMMENT) {
322               fTokenOffset = fOffset - 2;
323               fCurrentLength = 2;
324             }
325
326           } else if (!isInString(IPHPPartitions.PHP_PARTITIONING) && c != ICharacterScanner.EOF)
327             unread();
328           break;
329         case '*' :
330           if (!isInString(IPHPPartitions.PHP_PARTITIONING) && (c = read()) == '/') {
331             if (fContentType == IPHPPartitions.PHP_MULTILINE_COMMENT) {
332               fContentType = IPHPPartitions.PHP_PARTITIONING;
333               partitionBorder = true;
334               return getToken(IPHPPartitions.PHP_MULTILINE_COMMENT);
335             } else if (fContentType == IPHPPartitions.CSS_MULTILINE_COMMENT) {
336             } else if (fContentType == IPHPPartitions.JS_MULTILINE_COMMENT) {
337             }
338           } else if (fFileType == IPHPPartitions.SMARTY_FILE && (c = read()) == '}') {
339             if (DEBUG) {
340               System.out.println(
341                 "SMARTYDOC_TOKEN end "
342                   + fTokenOffset
343                   + " fContentType="
344                   + fContentType
345                   + " fLength="
346                   + fLength
347                   + " fOffset="
348                   + fOffset);
349             }
350             if (fContentType == IPHPPartitions.SMARTY_MULTILINE_COMMENT) {
351               fContentType = IPHPPartitions.HTML;
352               partitionBorder = true;
353               return getToken(IPHPPartitions.SMARTY_MULTILINE_COMMENT);
354             }
355           } else if (!isInString(IPHPPartitions.PHP_PARTITIONING) && c != ICharacterScanner.EOF) {
356             unread();
357           }
358           break;
359         case '\'' :
360           if (!fInDoubString)
361             fInString = !fInString;
362           break;
363         case '"' :
364           // toggle String mode
365           if (!fInString)
366             fInDoubString = !fInDoubString;
367           break;
368       }
369     } // end of file reached but we have to return the
370     // last partition.
371     return getToken(fContentType);
372   }
373   /* (non-Javadoc)
374    * @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
375    */
376   public void setRange(IDocument document, int offset, int length) {
377     if (DEBUG) {
378       System.out.println("SET RANGE: offset=" + offset + " length=" + length);
379     }
380
381     fDocument = document;
382     fOffset = offset;
383     fTokenOffset = offset;
384     fCurrentLength = 0;
385     fLength = 0;
386     fEnd = fOffset + length;
387     fInString = false;
388     fInDoubString = false;
389     fContentType = IPHPPartitions.HTML;
390     //        String[] prev = getPartitionStack(offset);
391   }
392
393   private int read() {
394     try {
395       if (fOffset < fEnd) {
396         fCurrentLength++;
397         return fDocument.getChar(fOffset++);
398       }
399       return ICharacterScanner.EOF;
400     } catch (BadLocationException e) {
401       // should never happen
402       // TODO write stacktrace to log
403       fOffset = fEnd;
404       return ICharacterScanner.EOF;
405     }
406   }
407
408   private void unread() {
409     --fOffset;
410     --fCurrentLength;
411   }
412
413   private void unread(int num) {
414     fOffset -= num;
415     fCurrentLength -= num;
416   }
417
418   private boolean checkPattern(char[] pattern) {
419     return checkPattern(pattern, false);
420   }
421
422   /**
423    * Check if next character sequence read from document is equals to 
424    * the provided pattern. Pattern is read from left to right until the 
425    * first character read doesn't match. If this happens all read characters are
426    * unread.
427    * @param pattern The pattern to check.
428    * @return <code>true</code> if pattern is equals else returns <code>false</code>.
429    */
430   private boolean checkPattern(char[] pattern, boolean ignoreCase) {
431     int prevOffset = fOffset;
432     int prevLength = fCurrentLength;
433     for (int i = 0; i < pattern.length; i++) {
434       int c = read();
435
436       if (c == ICharacterScanner.EOF || !letterEquals(c, pattern[i], ignoreCase)) {
437         fOffset = prevOffset;
438         fCurrentLength = prevLength;
439         return false;
440       }
441     }
442
443     return true;
444   }
445
446   private boolean letterEquals(int test, char letter, boolean ignoreCase) {
447     if (test == letter)
448       return true;
449     else if (ignoreCase && Character.isLowerCase(letter) && test == Character.toUpperCase(letter))
450       return true;
451     else if (ignoreCase && Character.isUpperCase(letter) && test == Character.toLowerCase(letter))
452       return true;
453
454     return false;
455   }
456
457   /**
458    * Checks wether the offset is in a <code>String</code> and the specified 
459    * contenttype is the current content type.
460    * Strings are delimited, mutual exclusive, by a " or by a '.
461    * 
462    * @param contentType The contenttype to check.
463    * @return <code>true</code> if the current offset is in a string else 
464    *                    returns false.
465    */
466   private boolean isInString(String contentType) {
467     if (fContentType == contentType)
468       return (fInString || fInDoubString);
469     else
470       return false;
471   }
472
473   /**
474    * Returns the previouse partition stack for the given offset.
475    * 
476    * @param offset The offset to return the previouse partitionstack for.
477    * 
478    * @return The stack as a string array.
479    */
480   private String[] getPartitionStack(int offset) {
481     ArrayList types = new ArrayList();
482     int tmpOffset = 0;
483     try {
484       ITypedRegion region = fDocument.getPartition(offset);
485       tmpOffset = region.getOffset();
486       while (tmpOffset - 1 > 0) {
487         region = fDocument.getPartition(tmpOffset - 1);
488         tmpOffset = region.getOffset();
489         types.add(0, region.getType());
490       }
491     } catch (BadLocationException e) {
492       if (DEBUG) {
493         e.printStackTrace();
494       }
495     }
496
497     String[] retVal = new String[types.size()];
498
499     retVal = (String[]) types.toArray(retVal);
500     return retVal;
501   }
502
503 }