KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > ext > html > HTMLSyntaxSupport


1
2 /*
3  * The contents of this file are subject to the terms of the Common Development
4  * and Distribution License (the License). You may not use this file except in
5  * compliance with the License.
6  *
7  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
8  * or http://www.netbeans.org/cddl.txt.
9  *
10  * When distributing Covered Code, include this CDDL Header Notice in each file
11  * and include the License file at http://www.netbeans.org/cddl.txt.
12  * If applicable, add the following below the CDDL Header, with the fields
13  * enclosed by brackets [] replaced by your own identifying information:
14  * "Portions Copyrighted [year] [name of copyright owner]"
15  *
16  * The Original Software is NetBeans. The Initial Developer of the Original
17  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
18  * Microsystems, Inc. All Rights Reserved.
19  */

20 package org.netbeans.editor.ext.html;
21
22 import org.netbeans.editor.ext.html.parser.SyntaxParser;
23 import org.netbeans.editor.ext.html.parser.SyntaxElement;
24 import java.util.*;
25 import javax.swing.text.BadLocationException JavaDoc;
26 import javax.swing.text.JTextComponent JavaDoc;
27 import org.netbeans.api.html.lexer.HTMLTokenId;
28 import org.netbeans.api.lexer.Token;
29 import org.netbeans.api.lexer.TokenHierarchy;
30 import org.netbeans.api.lexer.TokenSequence;
31 import org.netbeans.editor.BaseDocument;
32 import org.netbeans.editor.ext.ExtSyntaxSupport;
33 import org.netbeans.editor.ext.html.dtd.DTD;
34 import org.netbeans.editor.ext.html.dtd.DTD.Element;
35 import org.netbeans.editor.ext.html.dtd.InvalidateEvent;
36 import org.netbeans.editor.ext.html.dtd.InvalidateListener;
37
38
39 /**
40  * Support methods for HTML document syntax analyzes
41  *
42  * @author Petr Nejedly
43  * @author Marek Fukala
44  */

45 public class HTMLSyntaxSupport extends ExtSyntaxSupport implements InvalidateListener {
46     private static final String JavaDoc FALLBACK_DOCTYPE =
47             "-//W3C//DTD HTML 4.01 Transitional//EN"; // NOI18N
48

49     private DTD dtd;
50     private String JavaDoc docType;
51     private final SyntaxParser parser;
52     
53     /** Creates new HTMLSyntaxSupport */
54     public HTMLSyntaxSupport( BaseDocument doc ) {
55         super(doc);
56         parser = SyntaxParser.get(doc);
57     }
58     
59     /** Reset our cached DTD if no longer valid.
60      */

61     public void dtdInvalidated(InvalidateEvent evt) {
62         if( dtd != null && evt.isInvalidatedIdentifier( docType ) ) {
63             dtd = null;
64         }
65     }
66     
67     public DTD getDTD() {
68         String JavaDoc type = getDocType();
69         if( type == null ) type = FALLBACK_DOCTYPE;
70         
71         if( dtd != null && type == docType ) return dtd;
72         
73         docType = type;
74         dtd = org.netbeans.editor.ext.html.dtd.Registry.getDTD( docType, null );
75         return dtd;
76     }
77     
78     protected String JavaDoc getDocType() {
79         try {
80             SyntaxElement elem = getElementChain( 0 );
81             
82             if( elem == null ) return null; // empty document
83

84             int type = elem.getType();
85             
86             while( type != SyntaxElement.TYPE_DECLARATION
87                     && type != SyntaxElement.TYPE_TAG ) {
88                 elem = elem.getNext();
89                 if( elem == null ) break;
90                 type = elem.getType();
91             }
92             
93             if( type == SyntaxElement.TYPE_DECLARATION )
94                 return ((SyntaxElement.Declaration)elem).getPublicIdentifier();
95             
96             return null;
97         } catch( BadLocationException JavaDoc e ) {
98             return null;
99         }
100     }
101     
102     
103     
104     /** Find matching tags with the current position.
105      * @param offset position of the starting tag
106      * @param simple whether the search should skip comment and possibly other areas.
107      * This can be useful when the speed is critical, because the simple
108      * search is faster.
109      * @return array of integers containing starting and ending position
110      * of the block in the document. Null is returned if there's
111      * no matching block.
112      */

113     @Override JavaDoc public int[] findMatchingBlock(int offset, boolean simpleSearch)
114     throws BadLocationException JavaDoc {
115         BaseDocument document = getDocument();
116         document.readLock();
117         try {
118             TokenHierarchy hi = TokenHierarchy.get(document);
119             TokenSequence ts = tokenSequence(hi, offset);
120             if(ts == null) {
121                 //no suitable token sequence found
122
return null;
123             }
124             
125             ts.move(offset);
126             if(!ts.moveNext() && !ts.movePrevious()) {
127                 return null; //no token found
128
}
129             
130             Token token = ts.token();
131             
132             // if the carret is after HTML tag ( after char '>' ), ship inside the tag
133
if (token.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) {
134                 ts.moveIndex(ts.index() - 2);
135                 if(ts.moveNext()) {
136                     token = ts.token();
137                 }
138             }
139             
140             boolean isInside = false; // flag, whether the carret is somewhere in a HTML tag
141
if (isTagButNotSymbol(token)) {
142                 isInside = true; // the carret is somewhere in '<htmltag' or '</htmltag'
143
} else {
144                 if(ts.moveNext()) {
145                     token = ts.token();
146                     if(token.id() == HTMLTokenId.TAG_OPEN_SYMBOL) {
147                         //we are on opening symbol < or </
148
//so go to the next token which should be a TAG
149
//if the token is null or nor TAG there is nothing to match
150
if((token.id() == HTMLTokenId.TAG_CLOSE)
151                         || (token.id() == HTMLTokenId.TAG_OPEN)) {
152                             isInside = true; // we found a tag
153
} else {
154                             return null;
155                         }
156                     } else {
157                         //we are on closing symbol > or />
158
// find out whether the carret is inside an HTML tag
159
//try to find the beginning of the tag.
160
boolean found = false;
161                         while(!isTagButNotSymbol(token) && token.id() != HTMLTokenId.TAG_CLOSE_SYMBOL && ts.movePrevious()) {
162                             token = ts.token();
163                         }
164                         
165                         if (ts.index() != -1 && isTagButNotSymbol(token)) {
166                             isInside = true;
167                         }
168                     }
169                 } else {
170                     return null; //no token
171
}
172             }
173             
174             if (ts.index() != -1 && isTagButNotSymbol(token) && isInside){
175                 int start; // possition where the matched tag starts
176
int end; // possition where the matched tag ends
177
int poss = -1; // how many the same tags is inside the mathed tag
178

179                 String JavaDoc tag = token.text().toString().toLowerCase().trim();
180                 //test whether we are in a close tag
181
if (token.id() == HTMLTokenId.TAG_CLOSE) {
182                     //we are in a close tag
183
do {
184                         token = ts.token();
185                         if (isTagButNotSymbol(token)) {
186                             String JavaDoc tagName = token.text().toString().toLowerCase().trim();
187                             if (tagName.equals(tag)
188                             && (token.id() == HTMLTokenId.TAG_OPEN)
189                             && !isSingletonTag(ts)) {
190                                 //it's an open tag
191
if (poss == 0){
192                                     //get offset of previous token: < or </
193
ts.movePrevious();
194                                     start = ts.token().offset(hi);
195                                     ts.moveIndex(ts.index() + 2);
196                                     ts.moveNext();
197                                     Token tok = ts.token();
198                                     end = tok.offset(hi)+ (tok.id() == HTMLTokenId.TAG_CLOSE_SYMBOL ? tok.text().length() : 0);
199                                     return new int[] {start, end};
200                                 } else{
201                                     poss--;
202                                 }
203                             } else {
204                                 //test whether the tag is a close tag for the 'tag' tagname
205
if ((tagName.indexOf(tag) > -1)
206                                 && !isSingletonTag(ts)) {
207                                     poss++;
208                                 }
209                             }
210                         }
211                     } while(ts.movePrevious());
212                     
213                 } else{
214                     //we are in an open tag
215
if (tag.charAt(0) == '>')
216                         return null;
217                     
218                     //We need to find out whether the open tag is a singleton tag or not.
219
//In the first case no matching is needed
220
if(isSingletonTag(ts)) return null;
221                     
222                     do {
223                         token = ts.token();
224                         if (isTagButNotSymbol(token)) {
225                             String JavaDoc tagName = token.text().toString().toLowerCase().trim();
226                             if (tagName.equals(tag)
227                             && token.id() == HTMLTokenId.TAG_CLOSE){
228                                 if (poss == 0) {
229                                     //get offset of previous token: < or </
230
end = token.offset(hi) + token.text().length() + 1;
231                                     ts.movePrevious();
232                                     start = ts.token().offset(hi);
233                                     
234                                     do {
235                                         token = ts.token();
236                                     } while(ts.moveNext() && token.id() != HTMLTokenId.TAG_CLOSE_SYMBOL);
237                                     
238                                     if (ts.index() != -1) {
239                                         end = token.offset(hi)+token.text().length();
240                                     }
241                                     return new int[] {start, end};
242                                 } else
243                                     poss--;
244                             } else{
245                                 if (tagName.equals(tag)
246                                 && !isSingletonTag(ts)) {
247                                     poss++;
248                                 }
249                             }
250                         }
251                     } while (ts.moveNext());
252                 }
253             }
254             
255             ts.move(offset); //reset the token sequence to the original position
256
ts.moveNext();
257             token = ts.token();
258             
259             //match html comments
260
if(ts.index() != -1 && token.id() == HTMLTokenId.BLOCK_COMMENT) {
261                 String JavaDoc tokenImage = token.text().toString();
262                 if(tokenImage.startsWith("<!--") && (offset < (token.offset(hi)) + "<!--".length())) { //NOI18N
263
//start html token - we need to find the end token of the html comment
264
do {
265                         token = ts.token();
266                         tokenImage = token.text().toString();
267                         if((token.id() == HTMLTokenId.BLOCK_COMMENT)) {
268                             if(tokenImage.endsWith("-->")) {//NOI18N
269
//found end token
270
int end = token.offset(hi) + tokenImage.length();
271                                 int start = end - "-->".length(); //NOI18N
272
return new int[] {start, end};
273                             }
274                         } else break;
275                     } while(ts.moveNext());
276                 }
277                 
278                 if(tokenImage.endsWith("-->") && (offset >= (token.offset(hi)) + tokenImage.length() - "-->".length())) { //NOI18N
279
//end html token - we need to find the start token of the html comment
280
do {
281                         token = ts.token();
282                         if((token.id() == HTMLTokenId.BLOCK_COMMENT)) {
283                             if(token.text().toString().startsWith("<!--")) { //NOI18N
284
//found end token
285
int start = token.offset(hi);
286                                 int end = start + "<!--".length(); //NOI18N
287
return new int[] {start, end};
288                             }
289                         } else break;
290                         
291                     } while(ts.movePrevious());
292                 }
293             } //eof match html comments
294

295         } finally {
296             document.readUnlock();
297         }
298         return null;
299     }
300     
301     /** Finds out whether the given {@link TokenSequence}'s actual token is a part of a singleton tag (e.g. <div style=""/>).
302      * @ts TokenSequence positioned on a token within a tag
303      * @return true is the token is a part of singleton tag
304      */

305     public boolean isSingletonTag(TokenSequence ts) {
306         int tsIndex = ts.index(); //backup ts state
307
if(tsIndex != -1) { //test if we are on a token
308
try {
309                 do {
310                     Token ti = ts.token();
311                     if(ti.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) {
312                         if("/>".equals(ti.text().toString())) { // NOI18N
313
//it is a singleton tag => do not match
314
return true;
315                         }
316                         if(">".equals(ti.text().toString())) {
317                             break; // NOI18N
318
}
319                     }
320                     //break the loop on TEXT or on another open tag symbol
321
//(just to prevent long loop in case the tag is not closed)
322
if((ti.id() == HTMLTokenId.TEXT)
323                     || (ti.id() == HTMLTokenId.TAG_OPEN_SYMBOL)) {
324                         break;
325                     }
326                 } while(ts.moveNext());
327             }finally{
328                 ts.moveIndex(tsIndex); //backup the TokenSequence position
329
ts.moveNext();
330             }
331         } else {
332             //ts is rewinded out of tokens
333
}
334         return false;
335     }
336     
337     /** Returns SyntaxElement instance for block of tokens, which is either
338      * surrounding given offset, or is just after the offset.
339      * @param offset offset in document where to search for SyntaxElement
340      * @return SyntaxElement surrounding or laying after the offset
341      * or <CODE>null</CODE> if there is no element there (end of document)
342      */

343     public SyntaxElement getElementChain( int offset ) throws BadLocationException JavaDoc {
344         return parser.getElementChain(offset);
345     }
346     
347     /** The way how to get previous SyntaxElement in document. It is not intended
348      * for direct usage, and thus is not public. Usually, it is called from
349      * SyntaxElement's method getPrevious()
350      */

351     SyntaxElement getPreviousElement( int offset ) throws BadLocationException JavaDoc {
352         return offset == 0 ? null : getElementChain( offset - 1 );
353     }
354       
355     public List getPossibleEndTags( int offset, String JavaDoc prefix ) throws BadLocationException JavaDoc {
356         prefix = prefix.toUpperCase();
357         int prefixLen = prefix.length();
358         SyntaxElement elem = getElementChain( offset );
359         Stack stack = new Stack();
360         List result = new ArrayList();
361         Set found = new HashSet();
362         DTD dtd = getDTD();
363         
364         if(elem == null) {
365             if(offset > 0) {
366                 elem = getElementChain( offset - 1);
367                 if(elem == null) return result;
368             } else return result;
369         }
370         
371         int itemsCount = 0;
372         for( ; elem != null; elem = elem.getPrevious() ) {
373             if( elem.getType() == SyntaxElement.TYPE_ENDTAG) { // NOI18N
374
DTD.Element tag = dtd.getElement( ((SyntaxElement.Named)elem).getName().toUpperCase() );
375                 if(tag != null && !tag.isEmpty()) stack.push( ((SyntaxElement.Named)elem).getName().toUpperCase() );
376             } else if(elem.getType() == SyntaxElement.TYPE_TAG) { //now </ and > are returned as SyntaxElement.TAG so I need to filter them NOI18N
377
DTD.Element tag = dtd.getElement( ((SyntaxElement.Tag)elem).getName().toUpperCase() );
378                 
379                 if( tag == null ) continue; // Unknown tag - ignore
380
if( tag.isEmpty() ) continue; // ignore empty Tags - they are like start and imediate end
381

382                 String JavaDoc name = tag.getName();
383                 
384                 if( stack.empty() ) { // empty stack - we are on the same tree deepnes - can close this tag
385
if( name.startsWith( prefix ) && !found.contains( name ) ) { // add only new items
386
found.add( name );
387                         result.add( new HTMLCompletionQuery.EndTagItem( name, offset-2-prefixLen, prefixLen+2, name, itemsCount ) );
388                     }
389                     if( ! tag.hasOptionalEnd() ) break; // If this tag have required EndTag, we can't go higher until completing this tag
390
} else { // not empty - we match content of stack
391
if( stack.peek().equals( name ) ) { // match - close this branch of document tree
392
stack.pop();
393                     } else if( ! tag.hasOptionalEnd() ) break; // we reached error in document structure, give up
394
}
395             }
396         }
397         
398         return result;
399     }
400     
401     public List getAutocompletedEndTag(int offset) {
402         List l = new ArrayList();
403         try {
404             SyntaxElement elem = getElementChain( offset - 1);
405             if(elem != null && elem.getType() == SyntaxElement.TYPE_TAG) {
406                 String JavaDoc tagName = ((SyntaxElement.Named)elem).getName();
407                 //check if the tag has required endtag
408
Element dtdElem = getDTD().getElement(tagName.toUpperCase());
409                 if( dtdElem == null || !dtdElem.isEmpty()) {
410                     HTMLCompletionQuery.ResultItem eti = new HTMLCompletionQuery.AutocompleteEndTagItem(tagName, offset);
411                     l.add(eti);
412                 }
413             }
414         }catch(BadLocationException JavaDoc e) {
415             //just ignore
416
}
417         return l;
418     }
419     
420     public int checkCompletion(JTextComponent JavaDoc target, String JavaDoc typedText, boolean visible ) {
421         int retVal = COMPLETION_CANCEL;
422         int dotPos = target.getCaret().getDot();
423         BaseDocument doc = (BaseDocument)target.getDocument();
424         switch( typedText.charAt( typedText.length()-1 ) ) {
425             case '/':
426                 if (dotPos >= 2) { // last char before inserted slash
427
try {
428                         String JavaDoc txtBeforeSpace = doc.getText(dotPos-2, 2);
429                         if( txtBeforeSpace.equals("</") ) // NOI18N
430
return COMPLETION_POPUP;
431                     } catch (BadLocationException JavaDoc e) {}
432                 }
433                 break;
434             case ' ':
435                 doc.readLock();
436                 try {
437                     TokenHierarchy hi = TokenHierarchy.get(doc);
438                     TokenSequence ts = tokenSequence(hi, dotPos - 1);
439                     if(ts == null) {
440                         //no suitable token sequence found
441
return COMPLETION_POST_REFRESH;
442                     }
443                     
444                     ts.move(dotPos-1);
445                     if(ts.moveNext() || ts.movePrevious()) {
446                         if(ts.token().id() == HTMLTokenId.WS) {
447                             return COMPLETION_POPUP;
448                         }
449                     }
450                 }finally {
451                     doc.readUnlock();
452                 }
453                 break;
454             case '<':
455             case '&':
456                 return COMPLETION_POPUP;
457             case ';':
458                 return COMPLETION_HIDE;
459             case '>':
460                 HTMLSyntaxSupport sup = (HTMLSyntaxSupport)doc.getSyntaxSupport().get(HTMLSyntaxSupport.class);
461                 try {
462                     //check if the cursor is behind an open tag
463
SyntaxElement se = getElementChain(dotPos-1);
464                     if(se != null && se.getType() == SyntaxElement.TYPE_TAG) {
465                         return COMPLETION_POPUP;
466                     }
467                 }catch(BadLocationException JavaDoc e) {
468                     //do nothing
469
}
470                 return COMPLETION_HIDE;
471                 
472         }
473         return COMPLETION_POST_REFRESH;
474         
475     }
476     
477     
478     public static boolean isTag(Token t) {
479         return (( t.id() == HTMLTokenId.TAG_OPEN ) ||
480                 ( t.id() == HTMLTokenId.TAG_CLOSE ) ||
481                 ( t.id() == HTMLTokenId.TAG_OPEN_SYMBOL) ||
482                 ( t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL));
483     }
484     
485     public static boolean isTagButNotSymbol(Token t) {
486         return (( t.id() == HTMLTokenId.TAG_OPEN) ||
487                 ( t.id() == HTMLTokenId.TAG_CLOSE));
488     }
489     
490     private static TokenSequence tokenSequence(TokenHierarchy hi, int offset) {
491         TokenSequence ts = hi.tokenSequence(HTMLTokenId.language());
492         if(ts == null) {
493             //HTML language is not top level one
494
ts = hi.tokenSequence();
495             int diff = ts.move(offset);
496             if(!ts.moveNext() && !ts.movePrevious()) {
497                 return null; //no token found
498
} else {
499                 ts = ts.embedded(HTMLTokenId.language());
500             }
501         }
502         return ts;
503     }
504     
505 }
506
Popular Tags