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