1 19 package org.netbeans.editor.ext.html.dtd; 20 21 import java.io.Reader ; 22 import java.io.PushbackReader ; 23 import java.io.IOException ; 24 import java.util.*; 25 26 import org.netbeans.editor.ext.html.WeakHashSet; 27 28 33 class DTDParser extends Object { 34 35 private ReaderProvider provider = null; 37 38 private Reader getReader( String identifier, String fileName ) { 40 if( provider == null ) return null; 41 return provider.getReaderForIdentifier( identifier, fileName ); 42 } 43 44 46 private WeakHashSet stringCache = new WeakHashSet( 131, 0.75f ); 47 48 49 private WeakHashSet attributes = new WeakHashSet( 23, 0.75f ); 50 51 52 private WeakHashSet models = new WeakHashSet( 131, 0.75f ); 53 54 55 private WeakHashSet contents = new WeakHashSet( 131, 0.75f ); 56 57 60 Set leafs = new HashSet( 131, 0.75f ); 61 62 64 private SortedMap charRefs = new TreeMap(); 65 66 68 private SortedMap elementMap = new TreeMap(); 69 70 75 private Map entityMap = new HashMap(); 76 77 78 public DTD createDTD( ReaderProvider provider, String identifier, String fileName ) throws WrongDTDException { 79 this.provider = provider; 80 81 Reader reader = getReader( identifier, fileName ); 82 if( reader == null ) throw new WrongDTDException( "Can't open Reader for public identifier " + identifier ); try { 84 parseDTD( new PushbackReader ( reader, 1024*128 ) ); 85 } catch( IOException e ) { 86 throw new WrongDTDException( "IOException during parsing: " + e.getMessage() ); } 88 89 for( Iterator it = elementMap.values().iterator(); it.hasNext (); ) { 91 DTD.Element elem = (DTD.Element)it.next(); 92 ContentModelImpl cm = (ContentModelImpl)elem.getContentModel(); 93 94 Set newIncs = new HashSet(); 95 for( Iterator incIter = cm.included.iterator(); incIter.hasNext (); ) { 96 Object oldElem; 97 Object subElem = oldElem = incIter.next(); 98 if( subElem instanceof String ) { 99 subElem = elementMap.get( ((String )subElem).toUpperCase() ); 100 } 101 if( subElem == null ) { 102 throw new WrongDTDException( "'" + oldElem + "' element referenced from " + elem.getName() + " not found throughout the DTD." ); } 104 newIncs.add( subElem ); 105 } 106 cm.included = newIncs; 107 108 Set newExcs = new HashSet(); 109 for( Iterator excIter = cm.excluded.iterator(); excIter.hasNext (); ) { 110 Object oldElem; 111 Object subElem = oldElem = excIter.next(); 112 if( subElem instanceof String ) { 113 subElem = elementMap.get( ((String )subElem).toUpperCase() ); 114 } 115 if( subElem == null ) { 116 throw new WrongDTDException( "'" + oldElem + "' element referenced from " + elem.getName() + " not found throughout the DTD." ); } 118 newExcs.add( subElem ); 119 } 120 cm.excluded = newExcs; 121 cm.hashcode = cm.content.hashCode() + 2*cm.included.hashCode() + 3*cm.excluded.hashCode(); 122 } 123 124 125 for( Iterator it = leafs.iterator(); it.hasNext (); ) { 127 ContentLeafImpl leaf = (ContentLeafImpl)it.next(); 128 leaf.elem = (DTD.Element)elementMap.get( leaf.elemName ); 129 } 130 131 return new DTDImpl( identifier, elementMap, charRefs ); 132 } 133 134 135 137 void addEntity( String name, String content ) { 138 if( entityMap.get( name ) == null ) entityMap.put( name, content ); 139 } 140 141 143 void addPublicEntity( String name, String identifier, String file ) throws WrongDTDException { 144 if( entityMap.get( name ) == null ) { 145 146 StringBuffer sb = new StringBuffer (); 147 char[] buffer = new char[16384]; 148 Reader r = getReader( identifier, file ); 149 try { 150 int len; 151 while( (len = r.read( buffer )) >= 0 ) { 152 sb.append( buffer, 0, len ); 153 } 154 } catch( IOException e ) { 155 throw new WrongDTDException( "Error reading included public entity " + name + " - " + e.getMessage() ); } 157 158 entityMap.put( name, sb.toString() ); 159 } 160 } 161 162 DTD.Value createValue( String name ) { 163 return new ValueImpl( (String )stringCache.put( name ) ); 164 } 165 166 167 DTD.ContentModel createContentModel( DTD.Content content, Set included, Set excluded ) { 168 169 DTD.ContentModel cm = new ContentModelImpl( content, included, excluded ); 170 return (DTD.ContentModel)models.put( cm ); 171 } 172 173 174 DTD.Content createContentLeaf( String name ) { 175 DTD.Content c = new ContentLeafImpl( name ); 176 c = (DTD.Content)contents.put( c ); 177 leafs.add( c ); return c; 179 } 180 181 182 DTD.Content createContentNode( char type, DTD.Content subContent ) { 183 return (DTD.Content)contents.put( new UnaryContentNodeImpl( type, subContent ) ); 184 } 185 186 187 DTD.Content createContentNode( char type, DTD.Content[] subContent ) { 188 return (DTD.Content)contents.put( new MultiContentNodeImpl( type, subContent ) ); 189 } 190 191 DTD.Element createElement( String name, DTD.ContentModel cm, boolean optStart, boolean optEnd) { 192 DTD.Element retVal = new ElementImpl( name, cm, optStart, optEnd, new TreeMap() ); 193 return retVal; 194 } 195 196 197 DTD.Attribute createAttribute( String name, int type, String baseType, String typeHelper, String defaultMode, SortedMap values ) { 198 DTD.Attribute attr = new AttributeImpl( name, type, 199 (String )stringCache.put( baseType ), 200 (String )stringCache.put( typeHelper ), 201 (String )stringCache.put( defaultMode ), 202 values 203 ); 204 return (DTD.Attribute)attributes.put( attr ); 205 } 206 207 208 void addAttrToElement( String elemName, DTD.Attribute attr) throws WrongDTDException { 209 ElementImpl elem = (ElementImpl)elementMap.get( elemName.toUpperCase() ); 210 if( elem == null ) throw new WrongDTDException( "Attribute definition for unknown Element \"" + elemName +"\"." ); elem.addAttribute( attr ); 212 } 213 214 void createAddCharRef( String name, char value ) { 215 DTD.CharRef ref = new CharRefImpl( name, value ); 216 charRefs.put( name, ref ); 217 } 218 219 private boolean isNameChar( char c ) { 220 return Character.isLetterOrDigit( c ) || c == '_' || c == '-' || c == '.' || c == ':'; 221 } 222 223 224 225 226 227 private static final int DTD_INIT = 0; 228 private static final int DTD_LT = 1; private static final int DTD_EXC = 2; private static final int DTD_MINUS = 3; private static final int DTD_ACOMMENT = 4; 233 private void parseDTD( PushbackReader in ) throws IOException , WrongDTDException { 234 int state = DTD_INIT; 235 for( ;; ) { 236 int i = in.read(); 237 if( i == -1 ) { 238 break; 239 } 240 switch( state ) { 241 case DTD_INIT: 242 switch( i ) { 243 case '<': 244 state = DTD_LT; 245 break; 246 case '%': 247 parseEntityReference( in ); 248 break; } 250 break; 251 252 case DTD_LT: 253 if( i != '!' ) throw new WrongDTDException( "Unexpected char '" + (char)i + "' after '<'" ); state = DTD_EXC; 255 break; 256 257 case DTD_EXC: 258 switch( i ) { 259 case '-': 260 state = DTD_MINUS; 261 break; 262 case '[': 263 parseOptional( in ); 264 state = DTD_INIT; 265 break; 266 default: 267 in.unread( i ); 268 parseMarkup( in ); 269 state = DTD_INIT; 270 break; 271 } 272 break; 273 274 case DTD_MINUS: 275 if( i != '-' ) throw new WrongDTDException( "Unexpected char '" + (char)i + "' after \"<!-\"" ); parseComment( in ); 277 state = DTD_ACOMMENT; 278 break; 279 280 case DTD_ACOMMENT: 281 if( i != '>' ) throw new WrongDTDException( "Unexpected char '" + (char)i + "' after comment" ); state = DTD_INIT; 283 break; 284 285 } 286 } 287 if( state != DTD_INIT ) throw new WrongDTDException( "Premature end of DTD" ); } 289 290 294 private void parseMarkup( PushbackReader in ) throws IOException , WrongDTDException { 295 StringBuffer sb = new StringBuffer (); 296 for( ;; ) { 297 int i = in.read(); 298 if( i == -1 ) throw new WrongDTDException( "Premature end of DTD" ); if( i == ' ' ) break; 300 sb.append( (char)i ); } 302 303 String markup = sb.toString(); 304 305 if( "ENTITY".equals( markup ) ) { parseEntityDefinition( in ); 307 } else if( "ELEMENT".equals( markup ) ) { parseElement( in ); 309 } else if( "ATTLIST".equals( markup ) ) { parseAttlist( in ); 311 } else throw new WrongDTDException( "Wrong DTD markup <!" + markup ); } 313 314 315 private static final int PED_INIT = 0; 316 private static final int PED_PERCENT = 1; 317 private static final int PED_CHAR = 2; 318 private static final int PED_NAME = 3; 319 private static final int PED_ANAME = 4; 320 private static final int PED_VAL = 5; 321 private static final int PED_TYPE = 6; 322 private static final int PED_AVAL = 7; 323 private static final int PED_AVAL_M = 8; 324 private static final int PED_ATYPE = 9; 325 private static final int PED_ID = 10; 326 private static final int PED_AID = 11; 327 private static final int PED_FILE = 12; 328 private static final int PED_AFILE = 13; 329 private static final int PED_AFILE_M = 14; 330 private static final int PED_ACHAR = 15; 331 private static final int PED_CH_TYPE = 16; 332 private static final int PED_CH_ATYPE = 17; 333 private static final int PED_CH_QUOT = 18; 334 335 336 337 private void parseEntityDefinition( PushbackReader in ) throws IOException , WrongDTDException { 338 int state = PED_INIT; 339 StringBuffer name = new StringBuffer (); 340 StringBuffer value = new StringBuffer (); 341 StringBuffer type = new StringBuffer (); 342 StringBuffer identifier = new StringBuffer (); 343 344 for( ;; ) { 345 int i = in.read(); 346 if( i == -1 ) throw new WrongDTDException( "Premature end of DTD" ); switch( state ) { 348 case PED_INIT: 349 if( Character.isWhitespace( (char)i ) ) break; 350 if( i == '%' ) state = PED_PERCENT; 351 else { 352 name.append( (char)i ); 353 state = PED_CHAR; 354 } 355 break; 356 357 case PED_PERCENT: 358 if( Character.isWhitespace( (char)i ) ) break; 359 name.append( (char)i ); 360 state = PED_NAME; 361 break; 362 363 case PED_NAME: 364 if( Character.isWhitespace( (char)i ) ) { 365 state = PED_ANAME; 366 } else { 367 name.append( (char)i ); 368 } 369 break; 370 371 case PED_ANAME: 372 if( Character.isWhitespace( (char)i ) ) break; 373 if( i == '"' ) state = PED_VAL; 374 else { 375 in.unread( i ); 376 state = PED_TYPE; 377 } 378 break; 379 380 case PED_VAL: 381 if( i == '"' ) { 382 addEntity( name.toString(), value.toString() ); 383 state = PED_AVAL; 384 } else { 385 value.append( (char)i ); 386 } 387 break; 388 389 case PED_AVAL: 390 if( i == '>' ) { 391 return; 392 } 393 if( i == '-' ) state = PED_AVAL_M; 394 break; 395 396 case PED_AVAL_M: 397 if( i == '-' ) parseComment( in ); 398 state = PED_AVAL; 399 break; 400 401 case PED_TYPE: 402 if( Character.isWhitespace( (char)i ) ) { 403 if( type.toString().equals( "PUBLIC" ) ) { state = PED_ATYPE; 405 } else { 406 throw new WrongDTDException( "Unexpected entity type \"" + type + "\"." ); } 408 } else { 409 type.append( (char)i ); 410 } 411 break; 412 413 case PED_ATYPE: 414 if( Character.isWhitespace( (char)i ) ) break; 415 if( i == '"' ) { 416 state = PED_ID; 417 break; 418 } 419 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in PUBLIC entity." ); 421 case PED_ID: 422 if( i == '"' ) { 423 state = PED_AID; 424 } else { 425 identifier.append( (char)i ); 426 } 427 break; 428 429 case PED_AID: 430 if( Character.isWhitespace( (char)i ) ) break; 431 if( i == '"' ) { 432 state = PED_FILE; 433 break; 434 } 435 if( i == '>' ) { 436 addPublicEntity( name.toString(), identifier.toString(), null ); 437 return; 438 } 439 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in PUBLIC entity." ); 441 case PED_FILE: 442 if( i == '"' ) { 443 state = PED_AFILE; 444 } else { 445 value.append( (char)i ); 446 } 447 break; 448 449 case PED_AFILE: 450 if( Character.isWhitespace( (char)i ) ) break; 451 if( i == '-' ) { 452 state = PED_AFILE_M; 453 break; 454 } 455 if( i == '>' ) { 456 addPublicEntity( name.toString(), identifier.toString(), value.toString() ); 457 return; 458 } 459 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in PUBLIC entity." ); 461 case PED_AFILE_M: 462 if( i == '-' ) { 463 parseComment( in ); 464 state = PED_FILE; 465 break; 466 } 467 throw new WrongDTDException( "Unexpected sequence \"-" + (char)i + "\" in in PUBLIC entity." ); 469 case PED_CHAR: 470 if( Character.isWhitespace( (char)i ) ) { 471 state = PED_ACHAR; 472 } else { 473 name.append( (char)i ); 474 } 475 break; 476 477 case PED_ACHAR: 478 if( Character.isWhitespace( (char)i ) ) break; 479 else { 480 type.append( (char)i ); 481 state = PED_CH_TYPE; 482 } 483 break; 484 485 case PED_CH_TYPE: 486 if( Character.isWhitespace( (char)i ) ) { 487 if( type.toString().equals( "CDATA" ) ) { state = PED_ATYPE; 489 state = PED_CH_ATYPE; 490 } else { 491 throw new WrongDTDException( "Unexpected entity type \"" + type + "\"." ); } 493 } else { 494 type.append( (char)i ); 495 } 496 break; 497 498 case PED_CH_ATYPE: 499 if( Character.isWhitespace( (char)i ) ) break; 500 else if( i == '"' ) { 501 state = PED_CH_QUOT; 502 } else { 503 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in entity." ); } 505 break; 506 507 case PED_CH_QUOT: 508 if( i == '"' ) { 509 value.delete( 0, 2 ); 510 value.deleteCharAt( value.length() - 1 ); 511 int code = Integer.parseInt( value.toString() ); 512 createAddCharRef( name.toString(), (char)code ); 513 state = PED_AVAL; 514 } else { 515 value.append( (char)i ); 516 } 517 } 518 519 } 520 } 521 522 private static final int GR_INIT=0; 523 private static final int GR_NAME=1; 524 private static final int GR_ANAME=2; 525 527 private List parseGroup( PushbackReader in ) throws IOException , WrongDTDException { 528 int state = GR_INIT; 529 StringBuffer name = new StringBuffer (); 530 List list = new ArrayList(); 531 532 for( ;; ) { 533 int i = in.read(); 534 if( i == -1 ) throw new WrongDTDException( "Premature end of DTD" ); switch( state ) { 536 case GR_INIT: 537 if( Character.isWhitespace( (char)i ) ) break; 538 if( i == '%' ) { 539 parseEntityReference( in ); 540 } else { 541 name.append( (char)i ); 542 state = GR_NAME; 543 } 544 break; 545 546 case GR_NAME: 547 if( isNameChar( (char)i ) ) { 548 name.append( (char)i ); 549 break; 550 } 551 switch( i ) { 552 case ')': 553 list.add( name.toString() ); 554 return list; 555 case '|': 556 list.add( name.toString() ); 557 name.setLength( 0 ); 558 state = GR_INIT; 559 break; 560 default: 561 if( Character.isWhitespace( (char)i ) ) { 562 list.add( name.toString() ); 563 name.setLength( 0 ); 564 state = GR_ANAME; 565 break; 566 } else { 567 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in group definition." ); } 569 } 570 break; 571 572 case GR_ANAME: 573 if( Character.isWhitespace( (char)i ) ) break; 574 switch( i ) { 575 case ')': 576 return list; 577 case '|': 578 state = GR_INIT; 579 break; 580 default: 581 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in group definition." ); } 583 break; 584 } 585 } 586 587 } 588 589 private static final int EL_INIT = 0; 590 private static final int EL_NAME = 1; 591 private static final int EL_ANAME = 2; 592 private static final int EL_ASTART = 3; 593 private static final int EL_ACONTENT = 4; 594 private static final int EL_PLUS = 5; 595 private static final int EL_MINUS = 6; 596 597 601 private void parseElement( PushbackReader in ) throws IOException , WrongDTDException { 602 int state = EL_INIT; 603 StringBuffer name = new StringBuffer (); 604 List list = null; 605 boolean optStart = false; 606 boolean optEnd = false; 607 DTD.Content content = null; 608 Set inSet = new HashSet(); 609 Set exSet = new HashSet(); 610 611 for( ;; ) { 612 int i = in.read(); 613 if( i == -1 ) break; 614 switch( state ) { 615 case EL_INIT: 616 if( Character.isWhitespace( (char)i ) ) break; 617 switch( i ) { 618 case '(': 619 list = parseGroup( in ); 620 state = EL_ANAME; 621 break; 622 case '%': 623 parseEntityReference( in ); 624 break; default: 626 name.append( (char)i ); 627 state = EL_NAME; 628 break; 629 } 630 break; 631 632 case EL_NAME: 633 if( Character.isWhitespace( (char)i ) ) { 634 state = EL_ANAME; 635 list = new ArrayList(); 636 list.add( name.toString() ); 637 } else { 638 name.append( (char)i ); 639 } 640 break; 641 642 643 case EL_ANAME: 644 if( Character.isWhitespace( (char)i ) ) break; 645 switch( i ) { 646 case 'O': 647 optStart = true; case '-': 649 state = EL_ASTART; 650 break; 651 default: 652 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT optStart definition." ); } 654 break; 655 656 case EL_ASTART: 657 if( Character.isWhitespace( (char)i ) ) break; 658 switch( i ) { 659 case 'O': 660 optEnd = true; case '-': 662 content = parseContent( in ); 663 state = EL_ACONTENT; 664 break; 665 default: 666 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT optEnd definition." ); } 668 break; 669 670 case EL_ACONTENT: 671 if( Character.isWhitespace( (char)i ) ) break; 672 switch( i ) { 673 case '+': 674 state = EL_PLUS; 675 break; 676 case '-': 677 state = EL_MINUS; 678 break; 679 case '>': 680 DTD.ContentModel cm = createContentModel( content, inSet, exSet ); 681 for( Iterator iter = list.iterator(); iter.hasNext(); ) { 682 String key = ((String )iter.next()).toUpperCase(); 683 elementMap.put( key, createElement( key, cm, optStart, optEnd) ); 684 } 685 return; 686 default: 687 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT definition." ); } 689 break; 690 691 case EL_PLUS: 692 if( i == '(' ) { 693 state = EL_ACONTENT; 694 inSet.addAll( parseGroup( in ) ); 695 } else { 696 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT definition." ); } 698 break; 699 700 case EL_MINUS: 701 switch( i ) { 702 case '(': 703 state = EL_ACONTENT; 704 List l = parseGroup( in ); 705 exSet.addAll( l ); 706 break; 707 case '-': 708 state = EL_ACONTENT; 709 parseComment( in ); 710 break; 711 default: 712 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT definition." ); } 714 break; 715 } 716 } 717 718 } 720 721 private static final int CO_INIT = 0; 722 private static final int CO_NAME = 1; 723 private static final int CO_AMODEL = 2; 724 private static final int CO_AND = 3; 725 private static final int CO_OR = 4; 726 private static final int CO_SEQ = 5; 727 private static final int CO_AGROUP = 6; 728 730 private DTD.Content parseContent( PushbackReader in ) throws IOException , WrongDTDException { 731 int state = EL_INIT; 732 StringBuffer name = new StringBuffer (); 733 ArrayList list = null; 734 DTD.Content content = null; 735 736 for( ;; ) { 737 int i = in.read(); 738 if( i == -1 ) break; 739 switch( state ) { 740 case CO_INIT: 741 if( Character.isWhitespace( (char)i ) ) break; 742 switch( i ) { 743 case '%': 744 parseEntityReference( in ); 745 break; case '(': 747 content = parseContent( in ); 748 state = CO_AMODEL; 749 break; 750 default: 751 name.append( (char)i ); 752 state = CO_NAME; 753 break; 754 } 755 break; 756 757 case CO_NAME: 758 if( isNameChar( (char)i ) ) { 759 name.append( (char)i ); 760 } else { 761 switch( i ) { 762 case '?': 763 case '+': 764 case '*': 765 DTD.Content leaf = createContentLeaf( name.toString() ); 766 return createContentNode( (char)i, leaf ); 767 768 default: 769 in.unread( i ); 770 return createContentLeaf( name.toString() ); 771 } 772 } 773 break; 774 775 case CO_AMODEL: 776 if( Character.isWhitespace( (char)i ) ) break; 777 switch( i ) { 778 case '&': 779 list = new ArrayList(); 780 list.add( content ); 781 list.add( parseContent( in ) ); 782 state = CO_AND; 783 break; 784 case '|': 785 list = new ArrayList(); 786 list.add( content ); 787 list.add( parseContent( in ) ); 788 state = CO_OR; 789 break; 790 case ',': 791 list = new ArrayList(); 792 list.add( content ); 793 list.add( parseContent( in ) ); 794 state = CO_SEQ; 795 break; 796 case ')': 797 state = CO_AGROUP; 798 break; 799 default: 800 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ELEMENT optEnd definition." ); } 802 break; 803 804 case CO_AND: 805 if( Character.isWhitespace( (char)i ) ) break; 806 switch( i ) { 807 case '&': 808 list.add( parseContent( in ) ); 809 break; 810 case ')': 811 content = createContentNode( '&', (DTD.Content[])list.toArray( new DTD.Content[0] ) ); 812 state = CO_AGROUP; 813 break; 814 default: 815 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ContentModel definition." ); } 817 break; 818 819 case CO_OR: 820 if( Character.isWhitespace( (char)i ) ) break; 821 switch( i ) { 822 case '|': 823 list.add( parseContent( in ) ); 824 break; 825 case ')': 826 content = createContentNode( '|', (DTD.Content[])list.toArray( new DTD.Content[0] ) ); 827 state = CO_AGROUP; 828 break; 829 default: 830 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ContentModel definition." ); } 832 break; 833 834 case CO_SEQ: 835 if( Character.isWhitespace( (char)i ) ) break; 836 switch( i ) { 837 case ',': 838 list.add( parseContent( in ) ); 839 break; 840 case ')': 841 content = createContentNode( ',', (DTD.Content[])list.toArray( new DTD.Content[0] ) ); 842 state = CO_AGROUP; 843 break; 844 default: 845 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ContentModel definition." ); } 847 break; 848 849 case CO_AGROUP: 850 if( Character.isWhitespace( (char)i ) ) return content; 851 switch( i ) { 852 case '?': 853 case '+': 854 case '*': 855 return createContentNode( (char)i, content ); 856 default: 857 in.unread( i ); 858 return content; 859 } 860 } 861 } 862 863 throw new WrongDTDException( "Premature end of DTD" ); 865 } 866 private static final int ATT_INIT = 0; 867 private static final int ATT_NAME = 1; 868 private static final int ATT_ANAME = 2; 869 private static final int ATT_ANAME_M = 3; 870 private static final int ATT_VAR = 4; 871 private static final int ATT_AVAR = 5; 872 private static final int ATT_TYPE = 6; 873 private static final int ATT_ATYPE = 7; 874 private static final int ATT_MODE = 8; 875 private void parseAttlist( PushbackReader in ) throws IOException , WrongDTDException { 876 int state = ATT_INIT; 877 StringBuffer name = new StringBuffer (); 878 List list = null; StringBuffer attr = new StringBuffer (); List values = null; StringBuffer type = new StringBuffer (); String typeHelper = null; StringBuffer mode = new StringBuffer (); for( ;; ) { 885 int i = in.read(); 886 if( i == -1 ) break; 887 switch( state ) { 888 case ATT_INIT: 889 if( Character.isWhitespace( (char)i ) ) break; 890 switch( i ) { 891 case '%': 892 parseEntityReference( in ); 893 break; case '(': 895 list = parseGroup( in ); 896 state = ATT_ANAME; 897 break; 898 default: 899 name.append( (char)i ); 900 state = ATT_NAME; 901 break; 902 } 903 break; 904 905 case ATT_NAME: 906 if( Character.isWhitespace( (char)i ) ) { 907 list = new ArrayList(); 908 list.add( name.toString() ); 909 state = ATT_ANAME; 910 break; 911 } 912 name.append( (char)i ); 913 break; 914 915 case ATT_ANAME: 916 if( Character.isWhitespace( (char)i ) ) break; 917 switch( i ) { 918 case '%': 919 parseEntityReference( in ); 920 break; case '-': 922 state = ATT_ANAME_M; 923 break; 924 case '>': 925 return; 926 default: 927 attr.append( (char)i ); 928 state = ATT_VAR; 929 break; 930 } 931 break; 932 933 case ATT_ANAME_M: 934 if( i == '-' ) { 935 parseComment( in ); state = ATT_ANAME; 937 } else { 938 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in ATTLIST definition." ); } 940 break; 941 942 case ATT_VAR: 943 if( Character.isWhitespace( (char)i ) ) { 944 state = ATT_AVAR; 945 break; 946 } 947 attr.append( (char)i ); 948 break; 949 950 case ATT_AVAR: 951 if( Character.isWhitespace( (char)i ) ) break; 952 switch( i ) { 953 case '%': 954 typeHelper = parseEntityReference( in ); 955 break; case '(': 957 values = parseGroup( in ); 958 state = ATT_ATYPE; 959 break; 960 default: 961 type.append( (char)i ); 962 state = ATT_TYPE; 963 break; 964 } 965 break; 966 967 case ATT_TYPE: 968 if( Character.isWhitespace( (char)i ) ) { 969 state = ATT_ATYPE; 970 break; 971 } 972 type.append( (char)i ); 973 break; 974 975 case ATT_ATYPE: 976 if( Character.isWhitespace( (char)i ) ) break; 977 switch( i ) { 978 case '%': 979 parseEntityReference( in ); 980 break; default: 982 mode.append( (char)i ); 983 state = ATT_MODE; 984 break; 985 } 986 break; 987 988 case ATT_MODE: 989 if( Character.isWhitespace( (char)i ) ) { 990 DTD.Attribute a = null; 992 993 if( values == null ) { a = createAttribute( attr.toString(), 995 DTD.Attribute.TYPE_BASE, type.toString(), 996 typeHelper, mode.toString(), null ); 997 } else if( values.size() == 1 ) { 998 a = createAttribute( attr.toString(), 999 DTD.Attribute.TYPE_BOOLEAN, null, typeHelper, 1000 mode.toString(), null ); 1001 } else { 1002 SortedMap vals = new TreeMap(); 1003 for( Iterator iter = values.iterator(); iter.hasNext(); ) { 1004 String valName = ((String )iter.next()).toLowerCase(); 1005 vals.put( valName, createValue( valName ) ); 1006 } 1007 a = createAttribute( attr.toString(), 1008 DTD.Attribute.TYPE_SET, null, typeHelper, 1009 mode.toString(), vals ); 1010 } 1011 for( Iterator iter = list.iterator(); iter.hasNext(); ) { 1012 addAttrToElement( (String )iter.next(), a ); 1013 } 1014 1015 typeHelper = null; 1016 attr.setLength(0); 1017 type.setLength(0); 1018 mode.setLength(0); 1019 values = null; 1020 1021 state = ATT_ANAME; 1022 break; 1023 } 1024 mode.append( (char)i ); 1025 break; 1026 } 1027 } 1028 } 1029 1030 1031 private static final int OPT_INIT = 0; 1032 private static final int OPT_PROCESS = 1; 1033 private static final int OPT_APROCESS = 2; 1034 private static final int OPT_CONTENT = 3; 1035 private static final int OPT_BRAC1 = 4; 1036 private static final int OPT_BRAC2 = 5; 1037 1039 private void parseOptional( PushbackReader in ) throws IOException , WrongDTDException { 1040 int state = OPT_INIT; 1041 StringBuffer process = new StringBuffer (); 1042 StringBuffer content = new StringBuffer (); 1043 boolean ignore = false; 1044 1045 for( ;; ) { 1046 int i = in.read(); 1047 if( i == -1 ) break; switch( state ) { 1049 case OPT_INIT: 1050 if( Character.isWhitespace( (char)i ) ) break; 1051 if( i == '%' ) { 1052 parseEntityReference( in ); 1053 break; 1054 } 1055 process.append( (char)i ); 1056 state = OPT_PROCESS; 1057 break; 1058 1059 case OPT_PROCESS: 1060 if( Character.isWhitespace( (char)i ) ) { 1061 String s = process.toString(); 1062 if( "IGNORE".equals( s ) ) ignore = true; else if( ! "INCLUDE".equals( s ) ) throw new WrongDTDException( "Unexpected processing instruction " + s ); state = OPT_APROCESS; 1065 } else { 1066 process.append( (char)i ); 1067 } 1068 break; 1069 1070 case OPT_APROCESS: 1071 if( Character.isWhitespace( (char)i ) ) break; 1072 if( i == '[' ) state = OPT_CONTENT; 1073 else throw new WrongDTDException( "Unexpected char '" + (char)i + "' in processing instruction." ); break; 1075 1076 case OPT_CONTENT: 1077 if( i == ']' ) state = OPT_BRAC1; 1078 else content.append( (char)i ); 1079 break; 1080 1081 case OPT_BRAC1: 1082 if( i == ']' ) state = OPT_BRAC2; 1083 else { 1084 content.append( ']' ).append( (char)i ); 1085 state = OPT_CONTENT; 1086 } 1087 break; 1088 1089 case OPT_BRAC2: 1090 if( Character.isWhitespace( (char)i ) ) break; 1091 if( i == '>' ) { 1092 if( !ignore ) in.unread( content.toString().toCharArray() ); 1093 return; 1094 } 1095 throw new WrongDTDException( "Unexpected char '" + (char)i + "' in processing instruction." ); } 1097 } 1098 1099 } 1100 1101 private static final int COMM_TEXT = 0; private static final int COMM_DASH = 1; 1104 private void parseComment( PushbackReader in ) throws IOException , WrongDTDException { 1105 int state = COMM_TEXT; 1106 for( ;; ) { 1107 int i = in.read(); 1108 if( i == -1 ) break; switch( state ) { 1110 case COMM_TEXT: 1111 if( i == '-' ) state = COMM_DASH; 1112 break; 1113 case COMM_DASH: 1114 if( i == '-' ) return; state = COMM_TEXT; 1116 break; 1117 } 1118 } 1119 throw new WrongDTDException( "Premature end of DTD" ); } 1121 1122 1126 private String parseEntityReference( PushbackReader in ) throws IOException , WrongDTDException { 1127 StringBuffer sb = new StringBuffer (); 1128 for( ;; ) { 1129 int i = in.read(); 1130 if( i == -1 ) break; if( isNameChar( (char)i ) ) { 1132 sb.append( (char)i ); } else { 1134 String entValue = (String )entityMap.get( sb.toString() ); if( entValue == null ) 1136 throw new WrongDTDException( "No such entity: \"" + sb + "\"" ); 1138 if( i != ';' ) in.unread( i ); 1139 in.unread( entValue.toCharArray() ); return sb.toString(); 1141 } 1142 } 1143 throw new WrongDTDException( "Premature end of DTD" ); } 1145 1146 1147 public static class WrongDTDException extends Exception { 1148 public WrongDTDException( String reason ) { 1149 super( reason ); 1150 } 1151 } 1152 1153 1154 1155 1156 1157 1158 private static class DTDImpl implements DTD { 1159 private String id; 1160 private SortedMap elements; 1161 private SortedMap charRefs; 1162 1163 DTDImpl( String identifier, SortedMap elements, SortedMap charRefs ) { 1164 this.id = identifier; 1165 this.elements = elements; 1166 this.charRefs = charRefs; 1167 } 1168 1169 1170 public String getIdentifier() { 1171 return id; 1172 } 1173 1174 1175 public List getElementList( String prefix ) { 1176 List l = new ArrayList(); 1177 prefix = prefix == null ? "" : prefix.toUpperCase(); 1178 Iterator i = elements.tailMap( prefix ).entrySet().iterator(); 1179 1180 while( i.hasNext() ) { 1181 Map.Entry entry = (Map.Entry)i.next(); 1182 if( ((String )entry.getKey()).startsWith( prefix ) ) { 1183 l.add( entry.getValue() ); 1184 } else { break; } 1187 } 1188 1189 return l; 1190 } 1191 1192 1193 public DTD.Element getElement( String name ) { 1194 return (DTD.Element)elements.get( name ); 1195 } 1196 1197 1198 public List getCharRefList( String prefix ) { 1199 List l = new ArrayList(); 1200 Iterator i = charRefs.tailMap(prefix).entrySet().iterator(); 1201 1202 while( i.hasNext() ) { 1203 Map.Entry entry = (Map.Entry)i.next(); 1204 if( ((String )entry.getKey()).startsWith( prefix ) ) { 1205 l.add( entry.getValue() ); 1206 } else { break; } 1209 } 1210 1211 return l; 1212 } 1213 1214 1215 public DTD.CharRef getCharRef( String name ) { 1216 return (DTD.CharRef)charRefs.get( name ); 1217 } 1218 1219 public String toString() { 1220 return super.toString() + "[id=" + id + ", elements=" + elements + ",charRefs=" + charRefs + "]"; } 1222 } 1223 1224 1225 private static class ElementImpl implements DTD.Element { 1226 1227 private String name; 1228 private DTD.ContentModel model; 1229 private boolean optStart; 1230 private boolean optEnd; 1231 private SortedMap attributes; private DTD dtd; 1233 1234 1235 ElementImpl( String name, DTD.ContentModel model, boolean optStart, boolean optEnd, SortedMap attributes ) { 1236 this.name = name; 1237 this.model = model; 1238 this.optStart = optStart; 1239 this.optEnd = optEnd; 1240 this.attributes = attributes; 1241 } 1242 1243 1244 public String getName() { 1245 return name; 1246 } 1247 1248 1249 public boolean isEmpty() { 1250 if( optEnd && model.getContent() instanceof DTD.ContentLeaf ) return true; 1251 return false; 1253 } 1254 1255 1256 public boolean hasOptionalStart() { 1257 return optStart; 1258 } 1259 1260 1261 public boolean hasOptionalEnd() { 1262 return optEnd; 1263 } 1264 1265 1267 public List getAttributeList( String prefix ) { 1268 TreeSet set = new TreeSet(new Comparator() { 1269 public int compare( Object o1, Object o2 ) { 1270 if( isRequired( o1 ) && ! isRequired( o2 ) ) return -1; 1271 if( ! isRequired( o1 ) && isRequired( o2 ) ) return 1; 1272 return ((DTD.Attribute)o1).getName().compareTo( ((DTD.Attribute)o2).getName() ); 1273 } 1274 1275 private final boolean isRequired( Object o ) { 1276 return ((DTD.Attribute)o).getDefaultMode().equals( DTD.Attribute.MODE_REQUIRED ); 1277 } 1278 }); 1279 prefix = prefix.toLowerCase(); 1280 Iterator i = attributes.tailMap(prefix).entrySet().iterator(); 1281 1282 while( i.hasNext() ) { 1283 Map.Entry entry = (Map.Entry)i.next(); 1284 if( ((String )entry.getKey()).startsWith( prefix ) ) { 1285 set.add( entry.getValue() ); 1286 } else { break; } 1289 } 1290 return new ArrayList( set ); 1291 } 1292 1293 1294 public DTD.Attribute getAttribute( String name ) { 1295 return (DTD.Attribute)attributes.get( name ); 1296 } 1297 1298 void addAttribute( DTD.Attribute attr ) { 1299 attributes.put( attr.getName(), attr ); 1300 } 1301 1302 1303 public DTD.ContentModel getContentModel() { 1304 return model; 1305 } 1306 1307 public String toString() { 1308 return super.toString() + "[" + name + (optStart ? " O" : " -") + (optEnd ? " O " : " - ") + model + " attribs=" + attributes + "]"; } 1310 } 1311 1312 1313 public static class AttributeImpl implements DTD.Attribute { 1314 1315 private String name; 1316 private int type; 1317 private String baseType; 1318 private String typeHelper; 1319 private String defaultMode; 1320 private SortedMap values; 1321 private int hashcode; 1322 1323 public AttributeImpl( String name, int type, String baseType, String typeHelper, String defaultMode, SortedMap values ) { 1324 this.name = name; 1325 this.type = type; 1326 this.baseType = baseType; 1327 this.typeHelper = typeHelper; 1328 this.defaultMode = defaultMode; 1329 this.values = values; 1330 hashcode = name.hashCode() * (type + 1) * 1331 (baseType == null ? 1 : baseType.hashCode()) + 1332 (typeHelper == null ? 1 : typeHelper.hashCode()) + 1333 defaultMode.hashCode() + 1334 (values == null ? 1 : values.hashCode() ); 1335 } 1336 1337 1338 public String getName() { 1339 return name; 1340 } 1341 1342 1343 public int getType() { 1344 return type; 1345 } 1346 1347 public String getBaseType() { 1348 return baseType; 1349 } 1350 1351 1352 public String getTypeHelper() { 1353 return typeHelper; 1354 } 1355 1356 1357 public String getDefaultMode() { 1358 return defaultMode; 1359 } 1360 1361 1362 public boolean isRequired() { 1363 return defaultMode.equals( MODE_REQUIRED ); 1364 } 1365 1366 1373 public List getValueList( String prefix ) { 1374 if( type != TYPE_SET ) return null; 1375 1376 if( prefix == null ) prefix = ""; else prefix = prefix.toLowerCase(); 1377 1378 List retVal = new ArrayList(); 1379 Iterator i = values.tailMap(prefix).entrySet().iterator(); 1380 1381 while( i.hasNext() ) { 1382 Map.Entry entry = (Map.Entry)i.next(); 1383 if( ((String )entry.getKey()).startsWith( prefix ) ) { 1384 retVal.add( entry.getValue() ); 1385 } else { break; } 1388 } 1389 return retVal; 1390 } 1391 1392 1393 public DTD.Value getValue( String name ) { 1394 return (DTD.Value)values.get( name ); 1395 } 1396 1397 public String toString() { 1398 if( type == TYPE_SET ) { 1399 return name + " " + values + "[" + typeHelper + "] " + defaultMode; } else if( type == TYPE_BOOLEAN ) { 1401 return name + " (" + name + ")[" + typeHelper + "] " + defaultMode; } else { 1403 return name + " " + baseType + "[" + typeHelper + "] " + defaultMode; } 1405 } 1406 1407 public int hashCode() { 1408 return hashcode; 1409 } 1410 1411 public boolean equals( Object obj ) { 1412 if( !(obj instanceof AttributeImpl) ) return false; 1413 AttributeImpl a = (AttributeImpl)obj; 1414 return( 1415 hashcode == a.hashcode && 1416 name.equals( a.name ) && 1417 type == a.type && 1418 (baseType == a.baseType || baseType != null && baseType.equals( a.baseType ) ) && 1419 (typeHelper == a.typeHelper || typeHelper != null && typeHelper.equals( a.typeHelper ) ) && 1420 defaultMode.equals( a.defaultMode ) && 1421 (values == a.values || values != null && values.equals( a.values ) ) 1422 ); 1423 } 1424 } 1425 1426 1427 private static class ValueImpl implements DTD.Value { 1428 String name; 1429 1430 ValueImpl( String name ) { 1431 this.name = name; 1432 } 1433 1434 public String getName() { 1435 return name; 1436 } 1437 1438 public boolean equals( Object obj ) { 1439 if( ! (obj instanceof ValueImpl) ) return false; 1440 return name.equals( ((ValueImpl)obj).name ); 1441 } 1442 1443 public int hashCode() { 1444 return name.hashCode(); 1445 } 1446 1447 public String toString() { 1448 return name; 1449 } 1450 } 1451 1452 private static class CharRefImpl implements DTD.CharRef { 1453 private String name; 1454 private char value; 1455 1456 CharRefImpl( String name, char value ) { 1457 this.name = name; 1458 this.value = value; 1459 } 1460 1461 1462 public String getName() { 1463 return name; 1464 } 1465 1466 1467 public char getValue() { 1468 return value; 1469 } 1470 1471 public String toString() { 1472 return name + "->'" + value + "'(&#" + (int)value +";)"; } 1474 1475 public boolean equals( Object obj ) { 1476 if( ! (obj instanceof CharRefImpl) ) return false; 1477 return name.equals( ((CharRefImpl)obj).name ) && 1478 value == ((CharRefImpl)obj).value; 1479 } 1480 1481 public int hashCode() { 1482 return name.hashCode() * value; 1483 } 1484 } 1485 1486 1487 private static class ContentModelImpl implements DTD.ContentModel { 1488 int hashcode; 1489 DTD.Content content; 1490 Set included; 1491 Set excluded; 1492 1493 public ContentModelImpl( DTD.Content content, Set included, Set excluded ) { 1494 this.content = content; 1495 this.included = included; 1496 this.excluded = excluded; 1497 hashcode = content.hashCode() + 2*included.hashCode() + 3*excluded.hashCode(); 1498 } 1499 1500 1501 public DTD.Content getContent() { 1502 return content; 1503 } 1504 1505 1506 public Set getIncludes() { 1507 return included; 1508 } 1509 1510 1511 public Set getExcludes() { 1512 return excluded; 1513 } 1514 1515 public String toString() { 1516 StringBuffer sb = new StringBuffer ( content.toString() ); 1517 1518 if( ! included.isEmpty() ) { 1519 sb.append( " +(" ); Iterator i = included.iterator(); 1521 for( ;; ) { 1522 sb.append( ((DTD.Element)i.next()).getName() ); 1523 if( i.hasNext() ) sb.append( "|" ); else break; 1525 } 1526 sb.append( ")" ); } 1528 1529 if( ! excluded.isEmpty() ) { 1530 sb.append( " -(" ); Iterator i = excluded.iterator(); 1532 for( ;; ) { 1533 sb.append( ((DTD.Element)i.next()).getName() ); 1534 if( i.hasNext() ) sb.append( "|" ); else break; 1536 } 1537 sb.append( ")" ); } 1539 1540 return sb.toString(); 1541 } 1542 1543 public boolean equals( Object obj ) { 1544 if( ! (obj instanceof ContentModelImpl) ) return false; 1545 ContentModelImpl cmi = (ContentModelImpl)obj; 1546 return content.equals( cmi.content ) && 1547 included.equals( cmi.included ) && 1548 excluded.equals( cmi.excluded ); 1549 } 1550 1551 public int hashCode() { 1552 return hashcode; 1553 } 1554 1555 } 1556 1557 1558 static class ContentLeafImpl implements DTD.ContentLeaf { 1559 String elemName; 1560 DTD.Element elem; 1561 1562 public ContentLeafImpl( String name ) { 1563 this.elemName = name; 1564 } 1565 1566 1567 1568 public String getName() { 1569 return elemName; 1570 } 1571 1572 public DTD.Element getElement() { 1573 return elem; 1574 } 1575 1576 public boolean equals( Object obj ) { 1577 if( ! (obj instanceof ContentLeafImpl) ) return false; 1578 return elemName.equals( ((ContentLeafImpl)obj).elemName ); 1579 } 1580 1581 public int hashCode() { 1582 return elemName.hashCode(); 1583 } 1584 1585 public String toString() { 1586 return elemName; 1587 } 1588 1589 1590 public boolean isDiscardable() { 1591 return false; 1592 } 1593 1594 1596 public DTD.Content reduce(String elementName) { 1597 if( elemName.equals( elementName ) ) return EMPTY_CONTENT; 1598 return null; 1599 } 1600 1601 public Set getPossibleElements() { 1602 Set s = new HashSet(); 1603 s.add( elem ); 1604 return s; 1605 } 1606 } 1607 1608 1609 private static class UnaryContentNodeImpl implements DTD.ContentNode { 1610 int hashcode; 1611 char type; 1612 DTD.Content content; 1613 1614 1615 public UnaryContentNodeImpl( char type, DTD.Content content ) { 1616 if( type != '*' && type != '?' && type != '+' ) { 1618 throw new IllegalArgumentException ( "Unknown unary content type '" + type + "'" ); } 1620 1621 this.type = type; 1622 this.content = content; 1623 hashcode = type + content.hashCode(); 1624 } 1625 1626 1627 public boolean isLeaf() { return false; } 1628 1629 1630 public char getType() { return type; } 1631 1632 1633 public DTD.Content[] getContent() { 1634 return new DTD.Content[] { content }; 1635 } 1636 1637 public boolean equals( Object obj ) { 1638 if( !(obj instanceof UnaryContentNodeImpl) ) return false; 1639 return type == ((UnaryContentNodeImpl)obj).type && 1640 content.equals( ((UnaryContentNodeImpl)obj).content ); 1641 } 1642 1643 public int hashCode() { return hashcode; } 1644 1645 public String toString() { 1646 return content.toString() + type; 1647 } 1648 1649 public boolean isDiscardable() { 1650 if( type == '*' || type == '?' ) return true; 1651 return content.isDiscardable(); 1653 } 1654 1655 public DTD.Content reduce(String elementName) { 1656 DTD.Content sub = content.reduce( elementName ); 1657 if( sub == null ) return null; 1658 if( sub == EMPTY_CONTENT ) { 1659 if( type == '?' ) return EMPTY_CONTENT; 1660 if( type == '*' ) return this; 1661 return new UnaryContentNodeImpl( '*', content ); 1664 } 1665 if( type == '?' ) return sub; 1666 DTD.Content second = (type == '*') ? this : new UnaryContentNodeImpl( '*', content ); 1667 return new MultiContentNodeImpl( ',', new DTD.Content[] { sub, second } ); 1668 } 1669 1670 public Set getPossibleElements() { 1671 return content.getPossibleElements(); 1672 } 1673 1674 } 1675 1676 1677 1678 private static class MultiContentNodeImpl implements DTD.ContentNode { 1679 int hashcode; 1680 char type; 1681 DTD.Content[] content; 1682 1683 1684 public MultiContentNodeImpl( char type, DTD.Content[] content ) { 1685 if( type != '|' && type != '&' && type != ',' ) { 1687 throw new IllegalArgumentException ( "Unknown n-ary content type '" + type + "'" ); } 1689 1690 this.type = type; 1691 this.content = content; 1692 hashcode = type; 1693 for( int i=0; i<content.length; i++ ) { 1694 hashcode += content[i].hashCode(); 1695 } 1696 } 1697 1698 1699 public boolean isLeaf() { return false; } 1700 1701 1702 public char getType() { return type; } 1703 1704 1705 public DTD.Content[] getContent() { return content; } 1706 1707 public boolean equals( Object obj ) { 1708 if( ! (obj instanceof MultiContentNodeImpl) ) return false; 1709 return type == ((MultiContentNodeImpl)obj).type && 1710 Arrays.equals( content, ((MultiContentNodeImpl)obj).content ); 1711 } 1712 1713 public String toString() { 1714 StringBuffer sb = new StringBuffer ( "(" ); for( int i=0; i<content.length; i++ ) { 1716 sb.append( content[i].toString() ); 1717 if( i+1 < content.length ) sb.append( type ); 1718 } 1719 sb.append( ')' ); 1720 return sb.toString(); 1721 } 1722 1723 public int hashCode() { return hashcode; } 1724 1725 public boolean isDiscardable() { 1726 if( type == '&' || type == ',' ) { 1727 for( int i = 0; i < content.length; i++ ) { 1728 if( ! content[i].isDiscardable() ) return false; 1729 } 1730 return true; 1731 } 1732 for( int i = 0; i < content.length; i++ ) { 1734 if( content[i].isDiscardable() ) return true; 1735 } 1736 return false; 1737 } 1738 1739 public DTD.Content reduce(String elementName) { 1740 1741 if( type == '|' ) { 1742 for( int index = 0; index < content.length; index++ ) { 1743 DTD.Content sub = content[index].reduce( elementName ); 1744 if( sub != null ) return sub; 1745 } 1746 return null; 1747 } else if( type == ',' ) { 1748 int index = 0; 1750 1751 while( index < content.length) { 1752 DTD.Content sub = content[index].reduce( elementName ); 1753 if( sub == null && !content[index].isDiscardable() ) return null; 1755 1756 if( sub == EMPTY_CONTENT ) { 1758 int newLen = content.length - index - 1; 1759 if( newLen > 1 ) { DTD.Content[] newSub = new DTD.Content[newLen]; 1761 System.arraycopy( content, index + 1, newSub, 0, newLen ); 1762 return new MultiContentNodeImpl( ',', newSub ); 1763 } else { return content[index+1]; 1765 } 1766 } 1767 1768 if( sub != null ) { 1770 int newLen = content.length - index; 1771 if( newLen > 1 ) { DTD.Content[] newSub = new DTD.Content[newLen]; 1773 System.arraycopy( content, index + 1, newSub, 1, newLen-1 ); 1774 newSub[0] = sub; 1775 return new MultiContentNodeImpl( ',', newSub ); 1776 } else { return sub; 1778 } 1779 } 1780 index++; } 1782 1783 return null; } else { for( int index = 0; index < content.length; index++ ) { 1786 DTD.Content sub = content[index].reduce( elementName ); 1787 if( sub == EMPTY_CONTENT ) { 1788 int newLen = content.length - 1; 1789 if( newLen > 1 ) { 1790 DTD.Content[] newSub = new DTD.Content[ newLen ]; 1791 System.arraycopy( content, 0, newSub, 0, index ); 1792 if( index < newSub.length ) { 1793 System.arraycopy( content, index + 1, newSub, index, newLen - index ); 1794 } 1795 return new MultiContentNodeImpl( '&', newSub ); 1796 } else { 1797 return content[ 1 - index]; 1798 } 1799 } 1800 if( sub != null ) { 1801 DTD.Content right; 1802 if( content.length > 1 ) { 1803 int newLen = content.length - 1; 1804 DTD.Content[] newSub = new DTD.Content[ newLen ]; 1805 System.arraycopy( content, 0, newSub, 0, index ); 1806 if( index < newSub.length ) { 1807 System.arraycopy( content, index + 1, newSub, index, newLen - index ); 1808 } 1809 right = new MultiContentNodeImpl( '&', newSub ); 1810 } else { 1811 right = content[ 1 - index ]; 1812 } 1813 return new MultiContentNodeImpl( ',', new DTD.Content[] { sub, right } ); 1814 } 1815 } 1816 return null; 1817 1818 } 1819 } 1820 1821 public Set getPossibleElements() { 1822 Set retVal = new HashSet( 11 ); 1823 1824 if( type == '|' || type == '&' ) { 1825 for( int index = 0; index < content.length; index++ ) 1826 retVal.addAll( content[index].getPossibleElements() ); 1827 1828 } else { int index = 0; 1830 while( index < content.length) { 1831 retVal.addAll( content[index].getPossibleElements() ); 1832 if( !content[index].isDiscardable() ) break; 1833 index++; 1834 } 1835 } 1836 return retVal; 1837 } 1838 } 1839 1840 1841} 1842 | Popular Tags |