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