KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > text > syntax > XMLSyntaxSupport


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

19
20 package org.netbeans.modules.xml.text.syntax;
21
22 import java.lang.ref.*;
23 import java.util.*;
24 import java.io.*;
25
26 import javax.swing.text.*;
27 import javax.swing.event.DocumentListener JavaDoc;
28 import javax.swing.event.DocumentEvent JavaDoc;
29
30 import org.netbeans.editor.*;
31 import org.netbeans.editor.ext.*;
32
33 import org.netbeans.modules.xml.text.syntax.dom.*;
34 import org.netbeans.modules.xml.text.api.XMLDefaultTokenContext;
35 import org.openide.ErrorManager;
36 import org.openide.util.WeakListeners;
37
38 /**
39  * Creates higher level syntax elements (DOM nodes) above token chain.
40  * <p>
41  * It also defines rules for auto code completion poping up (Editor architecture issue).
42  *
43  * @author Petr Nejedly - original HTML code
44  * @author Sandeep S. Randhawa - XML port
45  * @author Petr Kuzel - use before strategy, use tokens whenever possible
46  * @version 0.8
47  */

48 public class XMLSyntaxSupport extends ExtSyntaxSupport implements XMLTokenIDs {
49     
50     private Reference reference = new SoftReference(null); // cached helper
51
private String JavaDoc systemId = null; // cached refernce to DTD
52
private String JavaDoc publicId = null; // cached refernce to DTD
53
private volatile boolean requestedAutoCompletion = false;
54     
55     /** Holds last character user have typed. */
56     private char lastInsertedChar = 'X'; // NOI18N
57

58     private final DocumentMonitor documentMonitor;
59     
60     private static final String JavaDoc CDATA_START = "<![CDATA[";
61     private static final String JavaDoc CDATA_END = "]]>";
62     
63     /** Creates new XMLSyntaxSupport */
64     public XMLSyntaxSupport(BaseDocument doc) {
65         super(doc);
66         
67         // listener has same lifetime as this class
68
documentMonitor = new DocumentMonitor();
69         DocumentListener JavaDoc l = WeakListeners.document(documentMonitor, doc);
70         doc.addDocumentListener(l);
71         
72     }
73     
74     /**
75      * Get token at given offet or previous one if at token boundary.
76      * It does not lock the document.
77      * @param offset valid position in document
78      * @return TokenItem or <code>null</code> at the document beginning.
79      */

80     public TokenItem getPreviousToken( int offset) throws BadLocationException {
81         
82         if (offset == 0) return null;
83         if (offset < 0) throw new BadLocationException("Offset " + offset + " cannot be less than 0.", offset); //NOI18N
84

85         // find first token item at the offset
86

87         TokenItem item = null;
88         int step = 11;
89         int len = getDocument().getLength(); //??? read lock
90
if (offset > len) throw new BadLocationException("Offset " + offset + " cannot be higher that document length " + len + " .", offset ); //NOI18N
91
int from = Math.min(len, offset);
92         int to = Math.min(len, offset);
93         
94         // go ahead to document beginning
95

96         while ( item == null) {
97             from = Math.max( from - step, 0);
98             if ( from == 0) {
99                 to = Math.min(to + step, len);
100             }
101             item = getTokenChain( from, to);
102             if ( from == 0 && to == len && item == null) {
103                 throw new IllegalStateException JavaDoc("Token at " + offset + " cannot be located!\nInspected range:[" + from + ", " + to + "]."); //NOI18N
104
}
105         }
106         
107         // if we are are at token boundary or at the fist document tokem all is OK
108
// otherwise the offset actually resides in some next token
109

110         while (item.getOffset() + item.getImage().length() < offset) { // it must cross or touch it
111
TokenItem next = item.getNext();
112             if (next == null) {
113                 if (item.getOffset() + item.getImage().length() >= len) {
114                     return item; // we are at boundary at the end of document
115
} else {
116                     throw new IllegalStateException JavaDoc("Token at " + offset + " cannot be located!\nPrevious token: " + item); //NOI18N
117
}
118             }
119             item = next;
120         }
121         
122         return item;
123     }
124     
125     /**
126      * Returns SyntaxElement instance for block of tokens, which is either
127      * surrounding given offset, or is just before the offset.
128      * @param offset Offset in document where to search for SyntaxElement.
129      * @return SyntaxElement Element surrounding or laying BEFORE the offset
130      * or <code>null</code> at document begining.
131      */

132     public SyntaxElement getElementChain( int offset ) throws BadLocationException {
133         
134         TokenItem item = getPreviousToken( offset);
135         if (item == null) return null;
136         
137         // locate SyntaxElement start boundary by traversing previous tokens
138
// then create element starting from that boundary
139

140         TokenID id = item.getTokenID();
141         TokenItem first = item;
142         
143         // reference can be in attribute or in content
144

145         if( id == CHARACTER ) {
146             while( id == CHARACTER ) {
147                 item = item.getPrevious();
148                 if (item == null) break;
149                 id = item.getTokenID();
150                 first = item;
151             }
152             
153             // #62654 incorrect syntax element create for reference when it is right after a tag ( <atag>&ref;... )
154
if(id == XMLDefaultTokenContext.TAG && item.getImage().endsWith(">")) {
155                 return createElement(item.getNext());
156             }
157             
158             // now item is either XMLSyntax.VALUE or we're in text, or at BOF
159
if( id != VALUE && id != TEXT && id != CDATA_SECTION ) {
160                 // #34453 it may start of element tag or end of start tag (skip attributtes)
161
if( id == XMLDefaultTokenContext.TAG ) {
162                     if( item.getImage().startsWith( "<" ) ) {
163                         return createElement( item ); // TAGO/ETAGO
164
} else {
165                         do {
166                             item = item.getPrevious();
167                             id = item.getTokenID();
168                         } while( id != XMLDefaultTokenContext.TAG );
169                         return createElement( item ); // TAGC
170
}
171                 }
172                 return createElement( first );
173             } // else ( for VALUE or TEXT ) fall through
174

175         }
176         
177         // these are possible only in containers (tags or doctype)
178
if ( id == XMLDefaultTokenContext.WS
179                 || id == XMLDefaultTokenContext.ARGUMENT
180                 || id == XMLDefaultTokenContext.OPERATOR
181                 || id == XMLDefaultTokenContext.VALUE) // or doctype
182
{
183             while (true) {
184                 item = item.getPrevious();
185                 id = item.getTokenID();
186                 if (id == XMLDefaultTokenContext.TAG) break;
187                 if (id == XMLDefaultTokenContext.DECLARATION
188                         && item.getImage().trim().length() > 0) break;
189                 if (isInPI(id, false)) break;
190             };
191         }
192         
193         if( id == TEXT) {
194             
195             while( id == TEXT || id == CHARACTER ) {
196                 first = item;
197                 item = item.getPrevious();
198                 if (item == null) break;
199                 id = item.getTokenID();
200             }
201             return createElement( first ); // from start of continuous text
202
}
203         
204         if( id == CDATA_SECTION) {
205             //the entire CDATA section is a one big fat token :-)
206
return createElement( item );
207         }
208         
209         //
210
// it may start of element tag or end of start tag (skip attributtes)
211
//
212
if( id == XMLDefaultTokenContext.TAG ) {
213             if( item.getImage().startsWith( "<" ) ) {
214                 return createElement( item ); // TAGO/ETAGO
215
} else {
216                 do {
217                     item = item.getPrevious();
218                     id = item.getTokenID();
219                 } while( id != XMLDefaultTokenContext.TAG );
220                 return createElement( item ); // TAGC
221
}
222         }
223         
224         if( id == XMLDefaultTokenContext.ERROR )
225             return new SyntaxElement.Error( this, item, getTokenEnd( item ) );
226         
227         if( id == XMLDefaultTokenContext.BLOCK_COMMENT ) {
228             while( id == XMLDefaultTokenContext.BLOCK_COMMENT && !item.getImage().startsWith( "<!--" ) ) { // NOI18N
229
first = item;
230                 item = item.getPrevious();
231                 id = item.getTokenID();
232             }
233             return createElement( first ); // from start of Commment
234
}
235         
236         
237         if ( id == XMLDefaultTokenContext.DECLARATION ) {
238             while(true) {
239                 first = item;
240                 if (id == XMLDefaultTokenContext.DECLARATION
241                         && item.getImage().startsWith("<!")) // NOI18N
242
{
243                     break;
244                 }
245                 item = item.getPrevious();
246                 if (item == null) break;
247                 id = item.getTokenID();
248             }
249             return createElement( first );
250         }
251         
252         // PI detection
253

254         if (isInPI(id, false)) {
255             do {
256                 item = item.getPrevious();
257                 id = item.getTokenID();
258             } while (id != XMLDefaultTokenContext.PI_START);
259         }
260         
261         if (id == XMLDefaultTokenContext.PI_START) {
262             return createElement(item);
263         }
264         
265         return null;
266     }
267     
268     // return if in PI exluding PI_START and including WSes
269
private boolean isInPI(TokenID id, boolean includeWS) {
270         return id == XMLDefaultTokenContext.PI_TARGET
271                 || id == XMLDefaultTokenContext.PI_CONTENT
272                 || id == XMLDefaultTokenContext.PI_END
273                 || (includeWS && id == XMLDefaultTokenContext.WS);
274     }
275     
276     /**
277      * Create elements starting with given item.
278      *
279      * @param item or null if EOD
280      * @return SyntaxElement startting at offset, or null, if EoD
281      */

282     public SyntaxElement createElement( TokenItem item ) throws BadLocationException {
283         
284         if( item == null ) return null; // on End of Document
285

286 // System.err.println("Creating element for: " + item.getTokenID().getName() + " " + item.getImage());
287

288         TokenID id = item.getTokenID();
289         TokenItem first = item;
290         int lastOffset = getTokenEnd( item );
291         switch (id.getNumericID()) {
292             
293             case XMLDefaultTokenContext.BLOCK_COMMENT_ID:
294                 
295                 while( id == XMLDefaultTokenContext.BLOCK_COMMENT ) {
296                     lastOffset = getTokenEnd( item );
297                     item = item.getNext();
298                     if( item == null ) break; //EoD
299
id = item.getTokenID();
300                 }
301                 return new CommentImpl( this, first, lastOffset );
302                 
303             case XMLDefaultTokenContext.DECLARATION_ID:
304                 
305                 // we treat internal DTD as one syntax element
306
boolean seekforDTDEnd = false;;
307                 while( id == XMLDefaultTokenContext.DECLARATION
308                         || id == XMLDefaultTokenContext.VALUE
309                         || seekforDTDEnd) {
310                     lastOffset = getTokenEnd( item );
311                     if (seekforDTDEnd) {
312                         if (item.getImage().endsWith("]>")) {
313                             break;
314                         }
315                     } else if (id == DECLARATION) {
316                         seekforDTDEnd = item.getImage().endsWith("[");
317                     }
318                     item = item.getNext();
319                     if( item == null ) break; //EoD
320
id = item.getTokenID();
321                 }
322                 return new DocumentTypeImpl( this, first, lastOffset);
323                 
324             case XMLDefaultTokenContext.ERROR_ID:
325                 
326                 return new SyntaxElement.Error( this, first, lastOffset);
327                 
328             case TEXT_ID:
329             case CHARACTER_ID:
330                 
331                 while( id == TEXT || id == CHARACTER || id == CDATA_SECTION) {
332                     lastOffset = getTokenEnd( item );
333                     item = item.getNext();
334                     if( item == null ) break; //EoD
335
id = item.getTokenID();
336                 }
337                 return new TextImpl( this, first, lastOffset );
338                 
339             case CDATA_SECTION_ID:
340                 return new CDATASectionImpl( this, first, first.getOffset() + first.getImage().length() );
341                 
342             case XMLDefaultTokenContext.TAG_ID:
343                 
344                 String JavaDoc text = item.getImage();
345                 if ( text.startsWith( "</" ) ) { // endtag // NOI18N
346
String JavaDoc name = text.substring( 2 );
347                     item = item.getNext();
348                     id = item == null ? null : item.getTokenID();
349                     
350                     while( id == XMLDefaultTokenContext.WS ) {
351                         lastOffset = getTokenEnd( item );
352                         item = item.getNext();
353                         id = item == null ? null : item.getTokenID();
354                     }
355                     
356                     if( id == XMLDefaultTokenContext.TAG && item.getImage().equals( ">" ) ) { // with this tag
357
return new EndTag( this, first, getTokenEnd( item ), name );
358                     } else { // without this tag
359
return new EndTag( this, first, lastOffset, name );
360                     }
361                 } else { // starttag
362
String JavaDoc name = text.substring( 1 );
363                     ArrayList attrs = new ArrayList();
364                     
365                     // skip attributes
366

367                     item = item.getNext();
368                     id = item == null ? null : item.getTokenID();
369                     
370                     while( id == XMLDefaultTokenContext.WS
371                             || id == XMLDefaultTokenContext.ARGUMENT
372                             || id == XMLDefaultTokenContext.OPERATOR
373                             || id == XMLDefaultTokenContext.VALUE
374                             || id == XMLDefaultTokenContext.CHARACTER) {
375                         if ( id == XMLDefaultTokenContext.ARGUMENT ) {
376                             attrs.add( item.getImage() ); // remember all attributes
377
}
378                         lastOffset = getTokenEnd( item );
379                         item = item.getNext();
380                         if (item == null) break;
381                         id = item.getTokenID();
382                     }
383                     
384                     // empty or start tag handling
385

386                     if( id == XMLDefaultTokenContext.TAG && (item.getImage().equals( "/>") || item.getImage().equals(">") || item.getImage().equals("?>"))){
387                         if(item.getImage().equals("/>"))
388                             return new EmptyTag( this, first, getTokenEnd( item ), name, attrs );
389                         else if(item.getImage().equals("?>"))
390                             return new EmptyTag( this, first, getTokenEnd( item ), name, attrs );
391                         else
392                             return new StartTag( this, first, getTokenEnd( item ), name, attrs );
393                     } else { // without this tag
394
return new StartTag( this, first, lastOffset, name, attrs );
395                     }
396                 }
397                 
398             case XMLDefaultTokenContext.PI_START_ID:
399                 do {
400                     lastOffset = getTokenEnd( item );
401                     item = item.getNext();
402                     if( item == null ) break; //EoD
403
id = item.getTokenID();
404                 } while( isInPI(id, true));
405                 return new ProcessingInstructionImpl( this, first, lastOffset);
406                 
407             default:
408                 // BadLocationException
409
}
410         
411         throw new BadLocationException( "Cannot create SyntaxElement at " + item, item.getOffset() ); //NOI18N
412
}
413     
414     // ~~~~~~~~~~~~~~~~~ utility methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
415

416     
417     /**
418      * Locate DOCTYPE from the start of document.
419      */

420 // public SyntaxElement.Declaration getDeclarationElement(){
421
// int offset = 5;
422
// SyntaxElement elem = null;
423
//
424
// try {
425
// while(true){ //??? optimalize stop on first element
426
// elem = getElementChain(offset);
427
// if(elem instanceof SyntaxElement.Declaration || elem == null)
428
// break;
429
// offset += elem.getElementLength()+1;
430
// }
431
// } catch (BadLocationException ble) {
432
// org.openide.TopManager.getDefault().notifyException(ble);
433
// }
434
// return elem != null ? (SyntaxElement.Declaration)elem : null;
435
// }
436

437     
438     /**
439      * Look for pairing closing tag.
440      *
441      * @param offset where to start the search
442      * @return name of pairing start tag
443      */

444     public String JavaDoc getEndTag(int offset) throws BadLocationException {
445         SyntaxElement elem = getElementChain( offset );
446         
447         if( elem != null ) {
448             elem = elem.getPrevious(); // we need smtg. before our </
449
} else { // End of Document
450
if( offset > 0 ) {
451                 elem = getElementChain( offset-1 );
452             } else { // beginning of document too, not much we can do on empty doc
453
return "";
454             }
455         }
456         
457         int counter = 0;
458         for( ; elem != null; elem = elem.getPrevious() ) {
459             //EMPTY TAG MUST MUST come before Tagcuz it extends from Tag
460
if(elem instanceof EmptyTag)
461                 continue;
462             else if(elem instanceof StartTag )
463                 counter++;
464             else if(elem instanceof EndTag)
465                 counter--;
466             else
467                 continue;
468             
469             if(counter == 1 ){
470                 String JavaDoc name = ((StartTag)elem).getTagName();
471                 return name;
472             }
473         }
474         return "";
475     }
476     
477     
478     /**
479      * @param offset of a child in parent
480      * @return all tags used as children of given parent that precedes the offset
481      */

482     public List getPreviousLevelTags( int offset) throws BadLocationException {
483         List result = new ArrayList();
484         Stack stack = new Stack();
485         Vector children = new Vector();
486         
487         SyntaxElement elem = getElementChain( offset );
488         if( elem != null ) {
489             elem = elem.getPrevious(); // we need smtg. before our </
490
} else { // End of Document
491
if( offset > 0 ) {
492                 elem = getElementChain( offset-1 );
493             } else { // beginning of document too, not much we can do on empty doc
494
return result;
495             }
496         }
497         
498         for( ; elem != null; elem = elem.getPrevious() ) {
499             if( elem instanceof EndTag )
500                 stack.push( ((EndTag)elem).getTagName() );
501             else if( elem instanceof EmptyTag ) {
502                 if(stack.size()==0)
503                     //here we r a child of level root element so add him
504
children.add(((EmptyTag)elem).getTagName() );
505                 continue;
506             }else if( elem instanceof Tag ) {
507                 String JavaDoc name = ((Tag)elem).getTagName();
508                 
509                 // if(name.equals(prefix))
510
// continue;
511

512                 if( stack.empty() ) { // empty stack - we are on the same tree deepnes - can close this tag
513
result.add(name);
514                     
515                     for(int k=children.size();k>0;k--){
516                         result.add(children.get(k-1));
517                     }
518                     
519                     return result;
520                 } else // not empty - we match content of stack
521
if( stack.peek().equals( name ) ) { // match - close this branch of document tree
522

523                     if(stack.size()==1)
524                         //we need this name to add to our list of tags before the point of insertion
525
//we r at depth 1
526
children.add(name);
527                     
528                     stack.pop();
529                     }
530             }
531         }
532         result.clear();
533         return result;
534     }
535     
536     /**
537      * @param offset of a child in parent
538      * @return all tags used as children of given parent that follows the offset
539      */

540     public List getFollowingLevelTags(int offset)throws BadLocationException{
541         Stack stack = new Stack();
542         Vector children = new Vector();
543         
544         SyntaxElement elem = getElementChain( offset );
545         if( elem != null ) {
546             elem = elem.getNext(); // we need smtg. before our </
547
} else { // End of Document
548
if( offset > 0 ) {
549                 elem = getElementChain( offset-1 );
550             } else { // beginning of document too, not much we can do on empty doc
551
return new ArrayList();
552             }
553         }
554         
555         for( ; elem != null; elem = elem.getNext() ) {
556             if( elem instanceof EmptyTag ) {
557                 if(stack.size()==0)
558                     //here we r a child of level root element so add him
559
children.add(((EmptyTag)elem).getTagName() );
560                 continue;
561             }else if( elem instanceof Tag ) {
562                 stack.push( ((Tag)elem).getTagName() );
563             }else if( elem instanceof EndTag ){
564                 String JavaDoc name = ((EndTag)elem).getTagName();
565                 
566                 if( stack.empty() ) { // empty stack - we are on the same tree deepnes and can return the children now
567
return children;
568                 } else if( stack.peek().equals( name ) ) { // not empty - we match content of stack
569
// match - close this branch of document tree
570
if(stack.size()==1)
571                         //we need this name to add to our list of tags before the point of insertion
572
//we r at depth 1
573
children.add(name);
574                     
575                     stack.pop();
576                 }
577             }
578         }
579         children.clear();
580         return children;
581     }
582     
583     
584     /**
585      * Defines <b>auto-completion</b> popup trigering criteria.
586      * @param typedText single last typed char
587      *
588      */

589     public int checkCompletion(JTextComponent target, String JavaDoc typedText, boolean visible ) {
590         
591         requestedAutoCompletion = false;
592         
593         if( !visible ) {
594             int retVal = COMPLETION_CANCEL;
595             switch( typedText.charAt( typedText.length()-1 ) ) {
596                 case '/':
597                     int dotPos = target.getCaret().getDot();
598                     BaseDocument doc = (BaseDocument)target.getDocument();
599                     if (dotPos >= 2) { // last char before inserted slash
600
try {
601                             String JavaDoc txtBeforeSpace = doc.getText(dotPos-2, 2);
602                             if( txtBeforeSpace.equals("</") ) // NOI18N
603
retVal = COMPLETION_POPUP;
604                         } catch (BadLocationException e) {
605                             ErrorManager.getDefault().notify(e);
606                         }
607                     }
608                     break;
609                     
610                 case '<':
611                 case '&':
612                 case '"':
613                 case '\'':
614                     retVal = COMPLETION_POPUP;
615                     break;
616                 case '>':
617                     dotPos = target.getCaret().getDot();
618                     try {
619                         SyntaxElement sel = getElementChain(dotPos);
620                         if(sel != null && sel instanceof StartTag) {
621                             retVal = COMPLETION_POPUP;
622                         }
623                     } catch (BadLocationException e) {
624                         //ignore
625
}
626                     break;
627             }
628             if (retVal == COMPLETION_POPUP) requestedAutoCompletion = true;
629             return retVal;
630         } else { // the pane is already visible
631
switch (typedText.charAt(0)) {
632                 case '>':
633                 case ';':
634                     return COMPLETION_HIDE;
635             }
636             //requestedAutoCompletion = true;
637
return COMPLETION_POST_REFRESH; //requery it
638
}
639     }
640     
641     /**
642      * Return true is this syntax requested auto completion.
643      * XMLCompletionQuery can utilize it to not show needless 'No suggestion.'.
644      */

645     public boolean requestedAutoCompletion() {
646         return requestedAutoCompletion;
647     }
648     
649     /**
650      * @return end offset of given item
651      */

652     static int getTokenEnd( TokenItem item ) {
653         return item.getOffset() + item.getImage().length();
654     }
655     
656     /** Returns last inserted character. It's most likely one recently typed by user. */
657     public final char lastTypedChar() {
658         return lastInsertedChar;
659     }
660     
661     /** Find matching tags with the current position.
662      * @param offset position of the starting tag
663      * @param simple whether the search should skip comment and possibly other areas.
664      * This can be useful when the speed is critical, because the simple
665      * search is faster.
666      * @return array of integers containing starting and ending position
667      * of the block in the document. Null is returned if there's
668      * no matching block.
669      */

670     public int[] findMatchingBlock(int offset, boolean simpleSearch)
671     throws BadLocationException {
672         // TODO - replanning to the other thread. Now it's in awt thread
673
TokenItem token = getTokenChain(offset, offset+1);
674         TokenItem tokenOnOffset = token;
675         
676         // if the carret is after char '>' or '/>', ship inside the tag
677
if (token != null &&
678                 token.getTokenID() == XMLTokenIDs.TAG &&
679                 token.getImage().endsWith(">")) token = token.getPrevious();
680         
681         if(tokenOnOffset == null) return null;
682         
683         //declaration matching e.g. (<!DOCTYPE tutorial SYSTEM "newXMLWizard.dtd">)
684
if(tokenOnOffset.getTokenID() == XMLTokenIDs.DECLARATION) {
685             String JavaDoc tokenImage = tokenOnOffset.getImage();
686             if(tokenImage.startsWith("<!")) { //NOI18N
687
//declaration start
688
TokenItem toki = tokenOnOffset;
689                 do {
690                     toki = toki.getNext();
691                 } while (toki != null && toki.getTokenID() != XMLTokenIDs.DECLARATION);
692                 
693                 if(toki != null && toki.getTokenID() == XMLTokenIDs.DECLARATION && toki.getImage().endsWith(">")) {
694                     int start = toki.getOffset();
695                     int end = toki.getOffset() + toki.getImage().length();
696                     return new int[] {start, end};
697                 }
698             }
699             if(tokenImage.endsWith(">") && (offset >= (tokenOnOffset.getOffset()) + tokenOnOffset.getImage().length() - ">".length())) { //NOI18N
700
//declaration end
701
TokenItem toki = tokenOnOffset;
702                 do {
703                     toki = toki.getPrevious();
704                 } while (toki != null && toki.getTokenID() != XMLTokenIDs.DECLARATION);
705                 if(toki != null && toki.getTokenID() == XMLTokenIDs.DECLARATION && toki.getImage().startsWith("<!")) {
706                     int start = toki.getOffset();
707                     //end = PI_START offset + PI_START length + PI_TARGET length
708
int end = toki.getOffset() + "<!".length();
709                     return new int[] {start, end};
710                 }
711             }
712             return null;
713         }
714         
715         //PI matching e.g. <?xml vertion="1.0"?> will match <?xml (PI-START + PI-TARGET) and ?> (PI-END)
716
if(tokenOnOffset.getTokenID() == XMLTokenIDs.PI_START ||
717                 tokenOnOffset.getTokenID() == XMLTokenIDs.PI_TARGET) {
718             //carret in on PI_START or PI_TARGET => find PI end
719
TokenItem toki = tokenOnOffset;
720             do {
721                 toki = toki.getNext();
722             } while (toki != null && toki.getTokenID() != XMLTokenIDs.PI_END);
723             if(toki != null && toki.getTokenID() == XMLTokenIDs.PI_END) {
724                 int start = toki.getOffset();
725                 int end = toki.getOffset() + toki.getImage().length();
726                 return new int[] {start, end};
727             }
728         } else if(tokenOnOffset.getTokenID() == XMLTokenIDs.PI_END) {
729             //carret is on PI_END => find PI start
730
TokenItem toki = tokenOnOffset;
731             do {
732                 toki = toki.getPrevious();
733             } while (toki != null && toki.getTokenID() != XMLTokenIDs.PI_START);
734             if(toki != null && toki.getTokenID() == XMLTokenIDs.PI_START) {
735                 int start = toki.getOffset();
736                 //end = PI_START offset + PI_START length + PI_TARGET length
737
int end = toki.getOffset() + toki.getImage().length() + toki.getNext().getImage().length();
738                 return new int[] {start, end};
739             }
740         }
741         
742         //CDATA matching
743
if(tokenOnOffset.getTokenID() == XMLTokenIDs.CDATA_SECTION) {
744             String JavaDoc tokenImage = tokenOnOffset.getImage();
745             
746             TokenItem toki = tokenOnOffset;
747             if(tokenImage.startsWith(CDATA_START) && (offset < (tokenOnOffset.getOffset()) + CDATA_START.length())) { //NOI18N
748
//CDATA section start
749
int start = toki.getOffset() + toki.getImage().length() - CDATA_END.length(); //NOI18N
750
int end = toki.getOffset() + toki.getImage().length();
751                 return new int[] {start, end};
752             }
753             if(tokenImage.endsWith(CDATA_END) && (offset >= (tokenOnOffset.getOffset()) + tokenOnOffset.getImage().length() - CDATA_END.length())) { //NOI18N
754
//CDATA section end
755
int start = toki.getOffset();
756                 int end = toki.getOffset() + CDATA_START.length(); //NOI18N
757
return new int[] {start, end};
758             }
759             return null;
760         }
761         
762         //match xml comments
763
if(tokenOnOffset.getTokenID() == XMLTokenIDs.BLOCK_COMMENT) {
764             String JavaDoc tokenImage = tokenOnOffset.getImage();
765             TokenItem toki = tokenOnOffset;
766             if(tokenImage.startsWith("<!--") && (offset < (tokenOnOffset.getOffset()) + "<!--".length())) { //NOI18N
767
//start html token - we need to find the end token of the html comment
768
while(toki != null) {
769                     if((toki.getTokenID() == XMLTokenIDs.BLOCK_COMMENT)) {
770                         if(toki.getImage().endsWith("-->")) {//NOI18N
771
//found end token
772
int start = toki.getOffset() + toki.getImage().length() - "-->".length(); //NOI18N
773
int end = toki.getOffset() + toki.getImage().length();
774                             return new int[] {start, end};
775                         }
776                     } else break;
777                     toki = toki.getNext();
778                 }
779             }
780             if(tokenImage.endsWith("-->") && (offset >= (tokenOnOffset.getOffset()) + tokenOnOffset.getImage().length() - "-->".length())) { //NOI18N
781
//end html token - we need to find the start token of the html comment
782
while(toki != null) {
783                     if((toki.getTokenID() == XMLTokenIDs.BLOCK_COMMENT)) {
784                         if(toki.getImage().startsWith("<!--")) { //NOI18N
785
//found end token
786
int start = toki.getOffset();
787                             int end = toki.getOffset() + "<!--".length(); //NOI18N
788
return new int[] {start, end};
789                         }
790                     } else break;
791                     toki = toki.getPrevious();
792                 }
793             }
794             return null;
795         } //eof match xml comments
796

797         
798         //tags matching
799
boolean isInside = false; // flag, whether the carret is somewhere in a HTML tag
800
if( token != null ) {
801             if (token.getImage().startsWith("<")) {
802                 isInside = true; // the carret is somewhere in '<htmltag' or '</htmltag'
803
} else {
804                 // find out whether the carret is inside an HTML tag
805
//try to find the beginning of the tag.
806
while (token!=null &&
807                         token.getTokenID() != XMLTokenIDs.TAG &&
808                         !token.getImage().startsWith("<"))
809                     token = token.getPrevious();
810                 if (token!=null && token.getTokenID() == XMLTokenIDs.TAG &&
811                         token.getImage().startsWith("<"))
812                     isInside = true;
813             }
814         }
815         
816         if (token != null && isInside){
817             int start; // possition where the matched tag starts
818
int end; // possition where the matched tag ends
819
int poss = -1; // how many the same tags is inside the mathed tag
820

821             //test whether we are in a close tag
822
if (token.getTokenID() == XMLTokenIDs.TAG && token.getImage().startsWith("</")) {
823                 //we are in a close tag
824
String JavaDoc tag = token.getImage().substring(2).trim().toLowerCase();
825                 while ( token != null){
826                     if (token.getTokenID() == XMLTokenIDs.TAG && !">".equals(token.getImage())) {
827                         if (token.getImage().substring(1).trim().toLowerCase().equals(tag)
828                         && !isSingletonTag(token)) {
829                             //it's an open tag
830
if (poss == 0){
831                                 //get offset of previous token: < or </
832
start = token.getOffset();
833                                 end = token.getOffset()+token.getImage().length();
834                                 //include the closing > token into the block if it follows the opentag token
835
TokenItem next = token.getNext();
836                                 if(next != null && next.getTokenID() == XMLTokenIDs.TAG && ">".equals(next.getImage()))
837                                     end++;
838                                 
839                                 return new int[] {start, end};
840                             } else{
841                                 poss--;
842                             }
843                         } else {
844                             //test whether the tag is a close tag for the 'tag' tagname
845
if ((token.getImage().substring(2).toLowerCase().indexOf(tag) > -1)
846                             && !isSingletonTag(token)) {
847                                 poss++;
848                             }
849                         }
850                     }
851                     token = token.getPrevious();
852                 }
853                 
854             } else{
855                 //we are in an open tag
856
//We need to find out whether the open tag is a singleton tag or not.
857
//In the first case no matching is needed
858
if(isSingletonTag(token)) return null;
859                 
860                 String JavaDoc tag = token.getImage().substring(1).toLowerCase();
861                 while ( token != null){
862                     if (token.getTokenID() == XMLTokenIDs.TAG && !">".equals(token.getImage())) {
863                         if (token.getImage().substring(2).trim().toLowerCase().equals(tag)) {
864                             //it's a close tag
865
if (poss == 0) {
866                                 //get offset of previous token: < or </
867
start = token.getOffset();
868                                 end = token.getOffset()+token.getImage().length()+1;
869                                 
870                                 return new int[] {start, end};
871                             } else
872                                 poss--;
873                         } else{
874                             if (token.getImage().substring(1).toLowerCase().equals(tag)
875                             && !isSingletonTag(token))
876                                 poss++;
877                         }
878                     }
879                     token = token.getNext();
880                 }
881             }
882         }
883         
884         return super.findMatchingBlock(offset, simpleSearch);
885     }
886     
887     /** Finds out whether the given tagTokenItem is a part of a singleton tag (e.g. <div style=""/>).
888      * @tagTokenItem a token item whithin a tag
889      * @return true is the token is a part of singleton tag
890      */

891     public boolean isSingletonTag(TokenItem tagTokenItem) {
892         TokenItem ti = tagTokenItem;
893         while(ti != null) {
894             if(ti.getTokenID() == XMLTokenIDs.TAG) {
895                 if("/>".equals(ti.getImage())) { // NOI18N
896
return true;
897                 }
898                 if(">".equals(ti.getImage())) return false; // NOI18N
899
}
900             //break the loop on TEXT or on another open tag symbol
901
//(just to prevent long loop in case the tag is not closed)
902
if(ti.getTokenID() == XMLTokenIDs.TEXT) break;
903             
904             
905             ti = ti.getNext();
906         }
907         return false;
908     }
909     
910     
911     
912     /** Keep track of last typed character */
913     private class DocumentMonitor implements DocumentListener JavaDoc {
914         
915         public void changedUpdate(DocumentEvent JavaDoc e) {
916         }
917         
918         public void insertUpdate(DocumentEvent JavaDoc e) {
919             int start = e.getOffset();
920             int len = e.getLength();
921             try {
922                 String JavaDoc s = e.getDocument().getText(start + len - 1, 1);
923                 lastInsertedChar = s.charAt(0);
924             } catch (BadLocationException e1) {
925                 ErrorManager err = ErrorManager.getDefault();
926                 err.notify(e1);
927             }
928         }
929         
930         public void removeUpdate(DocumentEvent JavaDoc e) {
931         }
932     }
933 }
934
935
Popular Tags