1 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 ; 28 import javax.swing.event.DocumentEvent ; 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 48 public class XMLSyntaxSupport extends ExtSyntaxSupport implements XMLTokenIDs { 49 50 private Reference reference = new SoftReference(null); private String systemId = null; private String publicId = null; private volatile boolean requestedAutoCompletion = false; 54 55 56 private char lastInsertedChar = 'X'; 58 private final DocumentMonitor documentMonitor; 59 60 private static final String CDATA_START = "<![CDATA["; 61 private static final String CDATA_END = "]]>"; 62 63 64 public XMLSyntaxSupport(BaseDocument doc) { 65 super(doc); 66 67 documentMonitor = new DocumentMonitor(); 69 DocumentListener l = WeakListeners.document(documentMonitor, doc); 70 doc.addDocumentListener(l); 71 72 } 73 74 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); 85 87 TokenItem item = null; 88 int step = 11; 89 int len = getDocument().getLength(); if (offset > len) throw new BadLocationException("Offset " + offset + " cannot be higher that document length " + len + " .", offset ); int from = Math.min(len, offset); 92 int to = Math.min(len, offset); 93 94 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 ("Token at " + offset + " cannot be located!\nInspected range:[" + from + ", " + to + "]."); } 105 } 106 107 110 while (item.getOffset() + item.getImage().length() < offset) { TokenItem next = item.getNext(); 112 if (next == null) { 113 if (item.getOffset() + item.getImage().length() >= len) { 114 return item; } else { 116 throw new IllegalStateException ("Token at " + offset + " cannot be located!\nPrevious token: " + item); } 118 } 119 item = next; 120 } 121 122 return item; 123 } 124 125 132 public SyntaxElement getElementChain( int offset ) throws BadLocationException { 133 134 TokenItem item = getPreviousToken( offset); 135 if (item == null) return null; 136 137 140 TokenID id = item.getTokenID(); 141 TokenItem first = item; 142 143 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 if(id == XMLDefaultTokenContext.TAG && item.getImage().endsWith(">")) { 155 return createElement(item.getNext()); 156 } 157 158 if( id != VALUE && id != TEXT && id != CDATA_SECTION ) { 160 if( id == XMLDefaultTokenContext.TAG ) { 162 if( item.getImage().startsWith( "<" ) ) { 163 return createElement( item ); } else { 165 do { 166 item = item.getPrevious(); 167 id = item.getTokenID(); 168 } while( id != XMLDefaultTokenContext.TAG ); 169 return createElement( item ); } 171 } 172 return createElement( first ); 173 } 175 } 176 177 if ( id == XMLDefaultTokenContext.WS 179 || id == XMLDefaultTokenContext.ARGUMENT 180 || id == XMLDefaultTokenContext.OPERATOR 181 || id == XMLDefaultTokenContext.VALUE) { 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 ); } 203 204 if( id == CDATA_SECTION) { 205 return createElement( item ); 207 } 208 209 if( id == XMLDefaultTokenContext.TAG ) { 213 if( item.getImage().startsWith( "<" ) ) { 214 return createElement( item ); } else { 216 do { 217 item = item.getPrevious(); 218 id = item.getTokenID(); 219 } while( id != XMLDefaultTokenContext.TAG ); 220 return createElement( item ); } 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( "<!--" ) ) { first = item; 230 item = item.getPrevious(); 231 id = item.getTokenID(); 232 } 233 return createElement( first ); } 235 236 237 if ( id == XMLDefaultTokenContext.DECLARATION ) { 238 while(true) { 239 first = item; 240 if (id == XMLDefaultTokenContext.DECLARATION 241 && item.getImage().startsWith("<!")) { 243 break; 244 } 245 item = item.getPrevious(); 246 if (item == null) break; 247 id = item.getTokenID(); 248 } 249 return createElement( first ); 250 } 251 252 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 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 282 public SyntaxElement createElement( TokenItem item ) throws BadLocationException { 283 284 if( item == null ) return null; 286 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; id = item.getTokenID(); 300 } 301 return new CommentImpl( this, first, lastOffset ); 302 303 case XMLDefaultTokenContext.DECLARATION_ID: 304 305 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; 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; 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 text = item.getImage(); 345 if ( text.startsWith( "</" ) ) { String 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( ">" ) ) { return new EndTag( this, first, getTokenEnd( item ), name ); 358 } else { return new EndTag( this, first, lastOffset, name ); 360 } 361 } else { String name = text.substring( 1 ); 363 ArrayList attrs = new ArrayList(); 364 365 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() ); } 378 lastOffset = getTokenEnd( item ); 379 item = item.getNext(); 380 if (item == null) break; 381 id = item.getTokenID(); 382 } 383 384 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 { 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; id = item.getTokenID(); 404 } while( isInPI(id, true)); 405 return new ProcessingInstructionImpl( this, first, lastOffset); 406 407 default: 408 } 410 411 throw new BadLocationException( "Cannot create SyntaxElement at " + item, item.getOffset() ); } 413 414 416 417 420 437 438 444 public String getEndTag(int offset) throws BadLocationException { 445 SyntaxElement elem = getElementChain( offset ); 446 447 if( elem != null ) { 448 elem = elem.getPrevious(); } else { if( offset > 0 ) { 451 elem = getElementChain( offset-1 ); 452 } else { return ""; 454 } 455 } 456 457 int counter = 0; 458 for( ; elem != null; elem = elem.getPrevious() ) { 459 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 name = ((StartTag)elem).getTagName(); 471 return name; 472 } 473 } 474 return ""; 475 } 476 477 478 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(); } else { if( offset > 0 ) { 492 elem = getElementChain( offset-1 ); 493 } else { 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 children.add(((EmptyTag)elem).getTagName() ); 505 continue; 506 }else if( elem instanceof Tag ) { 507 String name = ((Tag)elem).getTagName(); 508 509 512 if( stack.empty() ) { 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 if( stack.peek().equals( name ) ) { 523 if(stack.size()==1) 524 children.add(name); 527 528 stack.pop(); 529 } 530 } 531 } 532 result.clear(); 533 return result; 534 } 535 536 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(); } else { if( offset > 0 ) { 549 elem = getElementChain( offset-1 ); 550 } else { return new ArrayList(); 552 } 553 } 554 555 for( ; elem != null; elem = elem.getNext() ) { 556 if( elem instanceof EmptyTag ) { 557 if(stack.size()==0) 558 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 name = ((EndTag)elem).getTagName(); 565 566 if( stack.empty() ) { return children; 568 } else if( stack.peek().equals( name ) ) { if(stack.size()==1) 571 children.add(name); 574 575 stack.pop(); 576 } 577 } 578 } 579 children.clear(); 580 return children; 581 } 582 583 584 589 public int checkCompletion(JTextComponent target, String 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) { try { 601 String txtBeforeSpace = doc.getText(dotPos-2, 2); 602 if( txtBeforeSpace.equals("</") ) 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 } 626 break; 627 } 628 if (retVal == COMPLETION_POPUP) requestedAutoCompletion = true; 629 return retVal; 630 } else { switch (typedText.charAt(0)) { 632 case '>': 633 case ';': 634 return COMPLETION_HIDE; 635 } 636 return COMPLETION_POST_REFRESH; } 639 } 640 641 645 public boolean requestedAutoCompletion() { 646 return requestedAutoCompletion; 647 } 648 649 652 static int getTokenEnd( TokenItem item ) { 653 return item.getOffset() + item.getImage().length(); 654 } 655 656 657 public final char lastTypedChar() { 658 return lastInsertedChar; 659 } 660 661 670 public int[] findMatchingBlock(int offset, boolean simpleSearch) 671 throws BadLocationException { 672 TokenItem token = getTokenChain(offset, offset+1); 674 TokenItem tokenOnOffset = token; 675 676 if (token != null && 678 token.getTokenID() == XMLTokenIDs.TAG && 679 token.getImage().endsWith(">")) token = token.getPrevious(); 680 681 if(tokenOnOffset == null) return null; 682 683 if(tokenOnOffset.getTokenID() == XMLTokenIDs.DECLARATION) { 685 String tokenImage = tokenOnOffset.getImage(); 686 if(tokenImage.startsWith("<!")) { 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())) { 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 int end = toki.getOffset() + "<!".length(); 709 return new int[] {start, end}; 710 } 711 } 712 return null; 713 } 714 715 if(tokenOnOffset.getTokenID() == XMLTokenIDs.PI_START || 717 tokenOnOffset.getTokenID() == XMLTokenIDs.PI_TARGET) { 718 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 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 int end = toki.getOffset() + toki.getImage().length() + toki.getNext().getImage().length(); 738 return new int[] {start, end}; 739 } 740 } 741 742 if(tokenOnOffset.getTokenID() == XMLTokenIDs.CDATA_SECTION) { 744 String tokenImage = tokenOnOffset.getImage(); 745 746 TokenItem toki = tokenOnOffset; 747 if(tokenImage.startsWith(CDATA_START) && (offset < (tokenOnOffset.getOffset()) + CDATA_START.length())) { int start = toki.getOffset() + toki.getImage().length() - CDATA_END.length(); 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())) { int start = toki.getOffset(); 756 int end = toki.getOffset() + CDATA_START.length(); return new int[] {start, end}; 758 } 759 return null; 760 } 761 762 if(tokenOnOffset.getTokenID() == XMLTokenIDs.BLOCK_COMMENT) { 764 String tokenImage = tokenOnOffset.getImage(); 765 TokenItem toki = tokenOnOffset; 766 if(tokenImage.startsWith("<!--") && (offset < (tokenOnOffset.getOffset()) + "<!--".length())) { while(toki != null) { 769 if((toki.getTokenID() == XMLTokenIDs.BLOCK_COMMENT)) { 770 if(toki.getImage().endsWith("-->")) { int start = toki.getOffset() + toki.getImage().length() - "-->".length(); 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())) { while(toki != null) { 783 if((toki.getTokenID() == XMLTokenIDs.BLOCK_COMMENT)) { 784 if(toki.getImage().startsWith("<!--")) { int start = toki.getOffset(); 787 int end = toki.getOffset() + "<!--".length(); return new int[] {start, end}; 789 } 790 } else break; 791 toki = toki.getPrevious(); 792 } 793 } 794 return null; 795 } 797 798 boolean isInside = false; if( token != null ) { 801 if (token.getImage().startsWith("<")) { 802 isInside = true; } else { 804 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; int end; int poss = -1; 821 if (token.getTokenID() == XMLTokenIDs.TAG && token.getImage().startsWith("</")) { 823 String 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 if (poss == 0){ 831 start = token.getOffset(); 833 end = token.getOffset()+token.getImage().length(); 834 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 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 if(isSingletonTag(token)) return null; 859 860 String 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 if (poss == 0) { 866 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 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())) { return true; 897 } 898 if(">".equals(ti.getImage())) return false; } 900 if(ti.getTokenID() == XMLTokenIDs.TEXT) break; 903 904 905 ti = ti.getNext(); 906 } 907 return false; 908 } 909 910 911 912 913 private class DocumentMonitor implements DocumentListener { 914 915 public void changedUpdate(DocumentEvent e) { 916 } 917 918 public void insertUpdate(DocumentEvent e) { 919 int start = e.getOffset(); 920 int len = e.getLength(); 921 try { 922 String 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 e) { 931 } 932 } 933 } 934 935 | Popular Tags |