1: 
  37: 
  38: package ;
  39: 
  40: import ;
  41: import ;
  42: import ;
  43: import ;
  44: import ;
  45: import ;
  46: import ;
  47: import ;
  48: import ;
  49: 
  50: import ;
  51: import ;
  52: import ;
  53: import ;
  54: import ;
  55: import ;
  56: import ;
  57: import ;
  58: import ;
  59: 
  60: 
 198: public final class ValidationConsumer extends EventFilter
 199: {
 200:     
 201:     
 202:     private static final boolean        warnNonDeterministic = false;
 203: 
 204:     
 205:     private String              rootName;
 206:     private Stack               contentStack = new Stack ();
 207: 
 208:     
 209:     private boolean             disableDeclarations;
 210:     private boolean             disableReset;
 211: 
 212:     
 213:     
 214:     
 215:     
 216:     
 217:     
 218: 
 219:     
 220:     private Hashtable           elements = new Hashtable ();
 221: 
 222:     
 223:     
 224:     private Hashtable           ids = new Hashtable ();
 225: 
 226:     
 227:     
 228:     
 229:     private Vector              notations = new Vector (5, 5);
 230:     private Vector              nDeferred = new Vector (5, 5);
 231:     private Vector              unparsed = new Vector (5, 5);
 232:     private Vector              uDeferred = new Vector (5, 5);
 233: 
 234:         
 235:         
 236:         
 237: 
 238: 
 239: 
 240:     
 247:         
 248:             
 249:     public ValidationConsumer ()
 250:     {
 251:         this (null);
 252:     }
 253: 
 254:     
 260:         
 261:             
 262:             
 263:     public ValidationConsumer (EventConsumer next)
 264:     {
 265:         super (next);
 266: 
 267:         setContentHandler (this);
 268:         setDTDHandler (this);
 269:         try { setProperty (DECL_HANDLER, this); }
 270:         catch (Exception e) {  }
 271:         try { setProperty (LEXICAL_HANDLER, this); }
 272:         catch (Exception e) {  }
 273:     }
 274: 
 275: 
 276:     private static final String fakeRootName
 277:         = ":Nobody:in:their_Right.Mind_would:use:this-name:1x:";
 278: 
 279:     
 311:     public ValidationConsumer (
 312:         String          rootName,
 313:         String          publicId,
 314:         String          systemId,
 315:         String          internalSubset,
 316:         EntityResolver  resolver,
 317:         String          minimalDocument
 318:     ) throws SAXException, IOException
 319:     {
 320:         this (null);
 321: 
 322:         disableReset = true;
 323:         if (rootName == null)
 324:             rootName = fakeRootName;
 325: 
 326:         
 327:         
 328:         
 329:         
 330:         
 331:         
 332:         StringWriter    writer = new StringWriter ();
 333: 
 334:         writer.write ("<!DOCTYPE ");
 335:         writer.write (rootName);
 336:         if (systemId != null) {
 337:             writer.write ("\n  ");
 338:             if (publicId != null) {
 339:                 writer.write ("PUBLIC '");
 340:                 writer.write (publicId);
 341:                 writer.write ("'\n\t'");
 342:             } else
 343:                 writer.write ("SYSTEM '");
 344:             writer.write (systemId);
 345:             writer.write ("'");
 346:         }
 347:         writer.write (" [ ");
 348:         if (rootName == fakeRootName) {
 349:             writer.write ("\n<!ELEMENT ");
 350:             writer.write (rootName);
 351:             writer.write (" EMPTY>");
 352:         }
 353:         if (internalSubset != null)
 354:             writer.write (internalSubset);
 355:         writer.write ("\n ]>");
 356: 
 357:         if (minimalDocument != null) {
 358:             writer.write ("\n");
 359:             writer.write (minimalDocument);
 360:             writer.write ("\n");
 361:         } else {
 362:             writer.write (" <");
 363:             writer.write (rootName);
 364:             writer.write ("/>\n");
 365:         }
 366:         minimalDocument = writer.toString ();
 367: 
 368:         
 369:         
 370:         
 371:         XMLReader       producer;
 372: 
 373:         producer = XMLReaderFactory.createXMLReader ();
 374:         bind (producer, this);
 375: 
 376:         if (resolver != null)
 377:             producer.setEntityResolver (resolver);
 378: 
 379:         InputSource     in;
 380: 
 381:         in = new InputSource (new StringReader (minimalDocument));
 382:         producer.parse (in);
 383: 
 384:         disableDeclarations = true;
 385:         if (rootName == fakeRootName)
 386:             this.rootName = null;
 387:     }
 388: 
 389:     private void resetState ()
 390:     {
 391:         if (!disableReset) {
 392:             rootName = null;
 393:             contentStack.removeAllElements ();
 394:             elements.clear ();
 395:             ids.clear ();
 396: 
 397:             notations.removeAllElements ();
 398:             nDeferred.removeAllElements ();
 399:             unparsed.removeAllElements ();
 400:             uDeferred.removeAllElements ();
 401:         }
 402:     }
 403: 
 404: 
 405:     private void warning (String description)
 406:     throws SAXException
 407:     {
 408:         ErrorHandler            errHandler = getErrorHandler ();
 409:         Locator                 locator = getDocumentLocator ();
 410:         SAXParseException       err;
 411: 
 412:         if (errHandler == null)
 413:             return;
 414: 
 415:         if (locator == null)
 416:             err = new SAXParseException (description, null, null, -1, -1);
 417:         else
 418:             err = new SAXParseException (description, locator);
 419:         errHandler.warning (err);
 420:     }
 421: 
 422:     
 423:     private void error (String description)
 424:     throws SAXException
 425:     {
 426:         ErrorHandler            errHandler = getErrorHandler ();
 427:         Locator                 locator = getDocumentLocator ();
 428:         SAXParseException       err;
 429: 
 430:         if (locator == null)
 431:             err = new SAXParseException (description, null, null, -1, -1);
 432:         else
 433:             err = new SAXParseException (description, locator);
 434:         if (errHandler != null)
 435:             errHandler.error (err);
 436:         else    
 437:             throw err;
 438:     }
 439: 
 440:     private void fatalError (String description)
 441:     throws SAXException
 442:     {
 443:         ErrorHandler            errHandler = getErrorHandler ();
 444:         Locator                 locator = getDocumentLocator ();
 445:         SAXParseException       err;
 446: 
 447:         if (locator != null)
 448:             err = new SAXParseException (description, locator);
 449:         else
 450:             err = new SAXParseException (description, null, null, -1, -1);
 451:         if (errHandler != null)
 452:             errHandler.fatalError (err);
 453:         
 454:         throw err;
 455:     }
 456: 
 457: 
 458:     private static boolean isExtender (char c)
 459:     {
 460:         
 461:         return c == 0x00b7 || c == 0x02d0 || c == 0x02d1 || c == 0x0387
 462:                || c == 0x0640 || c == 0x0e46 || c == 0x0ec6 || c == 0x3005
 463:                || (c >= 0x3031 && c <= 0x3035)
 464:                || (c >= 0x309d && c <= 0x309e)
 465:                || (c >= 0x30fc && c <= 0x30fe);
 466:     }
 467: 
 468: 
 469:     
 470:     private boolean isName (String name, String context, String id)
 471:     throws SAXException
 472:     {
 473:         char    buf [] = name.toCharArray ();
 474:         boolean pass = true;
 475: 
 476:         if (!Character.isUnicodeIdentifierStart (buf [0])
 477:                 && ":_".indexOf (buf [0]) == -1)
 478:             pass = false;
 479:         else {
 480:             int max = buf.length;
 481:             for (int i = 1; pass && i < max; i++) {
 482:                 char c = buf [i];
 483:                 if (!Character.isUnicodeIdentifierPart (c)
 484:                         && ":-_.".indexOf (c) == -1
 485:                         && !isExtender (c))
 486:                     pass = false;
 487:             }
 488:         }
 489: 
 490:         if (!pass)
 491:             error ("In " + context + " for " + id
 492:                 + ", '" + name + "' is not a name");
 493:         return pass;    
 494:     }
 495: 
 496:     
 497:     private boolean isNmtoken (String nmtoken, String context, String id)
 498:     throws SAXException
 499:     {
 500:         char    buf [] = nmtoken.toCharArray ();
 501:         boolean pass = true;
 502:         int     max = buf.length;
 503: 
 504:         
 505: 
 506:         for (int i = 0; pass && i < max; i++) {
 507:                 char c = buf [i];
 508:             if (!Character.isUnicodeIdentifierPart (c)
 509:                     && ":-_.".indexOf (c) == -1
 510:                     && !isExtender (c))
 511:                 pass = false;
 512:         }
 513: 
 514:         if (!pass)
 515:             error ("In " + context + " for " + id
 516:                 + ", '" + nmtoken + "' is not a name token");
 517:         return pass;    
 518:     }
 519: 
 520:     private void checkEnumeration (String value, String type, String name)
 521:     throws SAXException
 522:     {
 523:         if (!hasMatch (value, type))
 524:             
 525:             error ("Value '" + value
 526:                 + "' for attribute '" + name
 527:                 + "' is not permitted: " + type);
 528:     }
 529: 
 530:     
 531:     
 532:     static boolean hasMatch (String value, String orList)
 533:     {
 534:         int len = value.length ();
 535:         int max = orList.length () - len;
 536: 
 537:         for (int start = 0;
 538:                 (start = orList.indexOf (value, start)) != -1;
 539:                 start++) {
 540:             char c;
 541: 
 542:             if (start > max)
 543:                 break;
 544:             c = orList.charAt (start - 1);
 545:             if (c != '|' && c != '(')
 546:                 continue;
 547:             c = orList.charAt (start + len);
 548:             if (c != '|' &&  c != ')')
 549:                 continue;
 550:             return true;
 551:         }
 552:         return false;
 553:     }
 554: 
 555:     
 561:     public void startDTD (String name, String publicId, String systemId)
 562:     throws SAXException
 563:     {
 564:         if (disableDeclarations)
 565:             return;
 566: 
 567:         rootName = name;
 568:         super.startDTD (name, publicId, systemId);
 569:     }
 570: 
 571:     
 577:     public void endDTD ()
 578:     throws SAXException
 579:     {
 580:         if (disableDeclarations)
 581:             return;
 582: 
 583:         
 584:         
 585:         
 586: 
 587:         
 588:         
 589:         int length = nDeferred.size ();
 590:         for (int i = 0; i < length; i++) {
 591:             String notation = (String) nDeferred.elementAt (i);
 592:             if (!notations.contains (notation)) {
 593:                 error ("A declaration referred to notation '" + notation
 594:                         + "' which was never declared");
 595:             }
 596:         }
 597:         nDeferred.removeAllElements ();
 598: 
 599:         
 600:         
 601:         length = uDeferred.size ();
 602:         for (int i = 0; i < length; i++) {
 603:             String entity = (String) uDeferred.elementAt (i);
 604:             if (!unparsed.contains (entity)) {
 605:                 error ("An attribute default referred to entity '" + entity
 606:                         + "' which was never declared");
 607:             }
 608:         }
 609:         uDeferred.removeAllElements ();
 610:         super.endDTD ();
 611:     }
 612: 
 613: 
 614:     
 615:     
 616:     
 617:     static final String types [] = {
 618:         "CDATA",
 619:         "ID", "IDREF", "IDREFS",
 620:         "NMTOKEN", "NMTOKENS",
 621:         "ENTITY", "ENTITIES"
 622:     };
 623: 
 624: 
 625:     
 632:     public void attributeDecl (
 633:         String eName,
 634:         String aName,
 635:         String type,
 636:         String mode,
 637:         String value
 638:     ) throws SAXException
 639:     {
 640:         if (disableDeclarations)
 641:             return;
 642: 
 643:         ElementInfo     info = (ElementInfo) elements.get (eName);
 644:         AttributeInfo   ainfo = new AttributeInfo ();
 645:         boolean         checkOne = false;
 646:         boolean         interned = false;
 647: 
 648:         
 649:         
 650:         for (int i = 0; i < types.length; i++) {
 651:             if (types [i].equals (type)) {
 652:                 type = types [i];
 653:                 interned = true;
 654:                 break;
 655:             }
 656:         }
 657:         if ("#FIXED".equals (mode))
 658:             mode = "#FIXED";
 659:         else if ("#REQUIRED".equals (mode))
 660:             mode = "#REQUIRED";
 661: 
 662:         ainfo.type = type;
 663:         ainfo.mode = mode;
 664:         ainfo.value = value;
 665: 
 666:         
 667:         if (info == null) {
 668:             info = new ElementInfo (eName);
 669:             elements.put (eName, info);
 670:         }
 671:         if ("ID" == type) {
 672:             checkOne = true;
 673:             if (!("#REQUIRED" == mode || "#IMPLIED".equals (mode))) {
 674:                 
 675:                 error ("ID attribute '" + aName
 676:                     + "' must be #IMPLIED or #REQUIRED");
 677:             }
 678: 
 679:         } else if (!interned && type.startsWith ("NOTATION ")) {
 680:             checkOne = true;
 681: 
 682:             
 683:             StringTokenizer     tokens = new StringTokenizer (
 684:                 type.substring (10, type.lastIndexOf (')')),
 685:                 "|");
 686:             while (tokens.hasMoreTokens ()) {
 687:                 String  token = tokens.nextToken ();
 688:                 if (!notations.contains (token))
 689:                     nDeferred.addElement (token);
 690:             }
 691:         }
 692:         if (checkOne) {
 693:             for (Enumeration e = info.attributes.keys ();
 694:                     e.hasMoreElements ();
 695:                     ) {
 696:                 String          name;
 697:                 AttributeInfo   ainfo2;
 698: 
 699:                 name = (String) e.nextElement ();
 700:                 ainfo2 = (AttributeInfo) info.attributes.get (name);
 701:                 if (type == ainfo2.type || !interned ) {
 702:                     
 703:                     
 704:                     error ("Element '" + eName
 705:                         + "' already has an attribute of type "
 706:                         + (interned ? "NOTATION" : type)
 707:                         + " ('" + name
 708:                         + "') so '" + aName
 709:                         + "' is a validity error");
 710:                 }
 711:             }
 712:         }
 713: 
 714:         
 715:         if (value != null) {
 716: 
 717:             if ("CDATA" == type) {
 718:                 
 719: 
 720:             } else if ("NMTOKEN" == type) {
 721:                 
 722:                 isNmtoken (value, "attribute default", aName);
 723: 
 724:             } else if ("NMTOKENS" == type) {
 725:                 
 726:                 StringTokenizer tokens = new StringTokenizer (value);
 727:                 if (!tokens.hasMoreTokens ())
 728:                     error ("Default for attribute '" + aName
 729:                         + "' must have at least one name token.");
 730:                 else do {
 731:                     String token = tokens.nextToken ();
 732:                     isNmtoken (token, "attribute default", aName);
 733:                 } while (tokens.hasMoreTokens ());
 734: 
 735:             } else if ("IDREF" == type || "ENTITY" == type) {
 736:                 
 737:                 
 738:                 isName (value, "attribute default", aName);
 739:                 if ("ENTITY" == type && !unparsed.contains (value))
 740:                     uDeferred.addElement (value);
 741: 
 742:             } else if ("IDREFS" == type || "ENTITIES" == type) {
 743:                 
 744:                 
 745:                 StringTokenizer names = new StringTokenizer (value);
 746:                 if (!names.hasMoreTokens ())
 747:                     error ("Default for attribute '" + aName
 748:                         + "' must have at least one name.");
 749:                 else do {
 750:                     String name = names.nextToken ();
 751:                     isName (name, "attribute default", aName);
 752:                     if ("ENTITIES" == type && !unparsed.contains (name))
 753:                         uDeferred.addElement (value);
 754:                 } while (names.hasMoreTokens ());
 755: 
 756:             } else if (type.charAt (0) == '('  ) {
 757:                 
 758:                 checkEnumeration (value, type, aName);
 759: 
 760:             } else if (!interned && checkOne) { 
 761:                 
 762:                 isName (value, "attribute default", aName);
 763: 
 764:                 
 765:                 if (!notations.contains (value))
 766:                     nDeferred.addElement (value);
 767: 
 768:                 
 769:                 checkEnumeration (value, type, aName);
 770: 
 771:             } else if ("ID" != type)
 772:                 throw new RuntimeException ("illegal attribute type: " + type);
 773:         }
 774: 
 775:         if (info.attributes.get (aName) == null)
 776:             info.attributes.put (aName, ainfo);
 777:         
 782: 
 783:         if ("xml:space".equals (aName)) {
 784:             if (!("(default|preserve)".equals (type)
 785:                     || "(preserve|default)".equals (type)
 786:                         
 787:                         
 788:                         
 789:                     || "(preserve)".equals (type)
 790:                     || "(default)".equals (type)
 791:                     ))
 792:                 error (
 793:                     "xml:space attribute type must be like '(default|preserve)'"
 794:                     + " not '" + type + "'"
 795:                     );
 796: 
 797:         }
 798:         super.attributeDecl (eName, aName, type, mode, value);
 799:     }
 800: 
 801:     
 807:     public void elementDecl (String name, String model)
 808:     throws SAXException
 809:     {
 810:         if (disableDeclarations)
 811:             return;
 812: 
 813:         ElementInfo     info = (ElementInfo) elements.get (name);
 814: 
 815:         
 816:         if (info == null) {
 817:             info = new ElementInfo (name);
 818:             elements.put (name, info);
 819:         }
 820:         if (info.model != null) {
 821:             
 822:             
 823:             error ("Element type '" + name
 824:                 + "' was already declared.");
 825:         } else {
 826:             info.model = model;
 827: 
 828:             
 829:             if (model.charAt (1) == '#')        
 830:                 info.getRecognizer (this);
 831:         }
 832:         super.elementDecl (name, model);
 833:     }
 834: 
 835:     
 839:     public void internalEntityDecl (String name, String value)
 840:     throws SAXException
 841:     {
 842:         if (!disableDeclarations)
 843:             super.internalEntityDecl (name, value);
 844:     }
 845: 
 846:     
 850:     public void externalEntityDecl (String name,
 851:         String publicId, String systemId)
 852:     throws SAXException
 853:     {
 854:         if (!disableDeclarations)
 855:             super.externalEntityDecl (name, publicId, systemId);
 856:     }
 857: 
 858: 
 859:     
 865:     public void notationDecl (String name, String publicId, String systemId)
 866:     throws SAXException
 867:     {
 868:         if (disableDeclarations)
 869:             return;
 870: 
 871:         notations.addElement (name);
 872:         super.notationDecl (name, publicId, systemId);
 873:     }
 874: 
 875:     
 881:     public void unparsedEntityDecl (
 882:         String name,
 883:         String publicId,
 884:         String systemId,
 885:         String notationName
 886:     ) throws SAXException
 887:     {
 888:         if (disableDeclarations)
 889:             return;
 890: 
 891:         unparsed.addElement (name);
 892:         if (!notations.contains (notationName))
 893:             nDeferred.addElement (notationName);
 894:         super.unparsedEntityDecl (name, publicId, systemId, notationName);
 895:     }
 896: 
 897: 
 898:     
 903:     public void startDocument ()
 904:     throws SAXException
 905:     {
 906:         resetState ();
 907:         super.startDocument ();
 908:     }
 909: 
 910: 
 911:     private static boolean isAsciiLetter (char c)
 912:     {
 913:         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
 914:     }
 915: 
 916: 
 917:     
 921:     public void skippedEntity (String name)
 922:     throws SAXException
 923:     {
 924:         fatalError ("may not skip entities");
 925:     }
 926: 
 927:     
 930:     private String expandDefaultRefs (String s)
 931:     throws SAXException
 932:     {
 933:         if (s.indexOf ('&') < 0)
 934:             return s;
 935: 
 936: 
 937:         String message = "Can't expand refs in attribute default: " + s;
 938:         warning (message);
 939: 
 940:         return s;
 941:     }
 942: 
 943:     
 948:     public void startElement (
 949:         String          uri,
 950:         String          localName,
 951:         String          qName,
 952:         Attributes      atts
 953:     ) throws SAXException
 954:     {
 955:         
 956:         
 957:         
 958:         if (contentStack.isEmpty ()) {
 959:             
 960:             if (!qName.equals (rootName)) {
 961:                 if (rootName == null)
 962:                     warning ("This document has no DTD, can't be valid");
 963:                 else
 964:                     error ("Root element type '" + qName
 965:                         + "' was declared to be '" + rootName + "'");
 966:             }
 967:         } else {
 968:             Recognizer state = (Recognizer) contentStack.peek ();
 969: 
 970:             if (state != null) {
 971:                 Recognizer newstate = state.acceptElement (qName);
 972: 
 973:                 if (newstate == null)
 974:                     error ("Element type '" + qName
 975:                         + "' in element '" + state.type.name
 976:                         + "' violates content model " + state.type.model
 977:                         );
 978:                 if (newstate != state) {
 979:                     contentStack.pop ();
 980:                     contentStack.push (newstate);
 981:                 }
 982:             }
 983:         }
 984: 
 985:         
 986:         
 987:         
 988:         
 989:         
 990:         
 991:         
 992:         
 993:         
 994:         ElementInfo             info;
 995: 
 996:         info = (ElementInfo) elements.get (qName);
 997:         if (info == null || info.model == null) {
 998:             
 999:             error ("Element type '" + qName + "' was not declared");
1000:             contentStack.push (null);
1001: 
1002:             
1003:             elementDecl (qName, "ANY");
1004:         } else
1005:             contentStack.push (info.getRecognizer (this));
1006: 
1007:         
1008:         
1009:         
1010:         int                     len;
1011:         String                  aname;
1012:         AttributeInfo           ainfo;
1013: 
1014:         if (atts != null)
1015:             len = atts.getLength ();
1016:         else
1017:             len = 0;
1018: 
1019:         for (int i = 0; i < len; i++) {
1020:             aname = atts.getQName (i);
1021: 
1022:             if (info == null
1023:                     || (ainfo = (AttributeInfo) info.attributes.get (aname))
1024:                             == null) {
1025:                 
1026:                 error ("Attribute '" + aname
1027:                     + "' was not declared for element type " + qName);
1028:                 continue;
1029:             }
1030: 
1031:             String value = atts.getValue (i);
1032: 
1033:             
1034:             
1035: 
1036:             if ("#FIXED" == ainfo.mode) {
1037:                 String expanded = expandDefaultRefs (ainfo.value);
1038: 
1039:                 
1040:                 if (!value.equals (expanded)) {
1041:                     error ("Attribute '" + aname
1042:                         + "' must match " + expanded
1043:                         );
1044:                     continue;
1045:                 }
1046:             }
1047: 
1048:             if ("CDATA" == ainfo.type)
1049:                 continue;
1050: 
1051:             
1052:             
1053:             
1054:             
1055: 
1056:             if ("ID" == ainfo.type) {
1057:                 
1058:                 if (isName (value, "ID attribute", aname)) {
1059:                     if (Boolean.TRUE == ids.get (value))
1060:                         
1061:                         error ("ID attribute " + aname
1062:                             + " uses an ID value '" + value
1063:                             + "' which was already declared.");
1064:                     else
1065:                         
1066:                         ids.put (value, Boolean.TRUE);
1067:                 }
1068:                 continue;
1069:             }
1070: 
1071:             if ("IDREF" == ainfo.type) {
1072:                 
1073:                 if (isName (value, "IDREF attribute", aname)) {
1074:                     
1075:                     if (ids.get (value) == null)
1076:                         
1077:                         ids.put (value, Boolean.FALSE);
1078:                 }
1079:                 continue;
1080:             }
1081: 
1082:             if ("IDREFS" == ainfo.type) {
1083:                 StringTokenizer tokens = new StringTokenizer (value, " ");
1084: 
1085:                 if (!tokens.hasMoreTokens ()) {
1086:                     
1087:                     error ("IDREFS attribute " + aname
1088:                         + " must have at least one ID ref");
1089:                 } else do {
1090:                     String id = tokens.nextToken ();
1091: 
1092:                     
1093:                     if (isName (id, "IDREFS attribute", aname)) {
1094:                         
1095:                         if (ids.get (id) == null)
1096:                             
1097:                             ids.put (id, Boolean.FALSE);
1098:                     }
1099:                 } while (tokens.hasMoreTokens ());
1100:                 continue;
1101:             }
1102: 
1103:             if ("NMTOKEN" == ainfo.type) {
1104:                 
1105:                 isNmtoken (value, "NMTOKEN attribute", aname);
1106:                 continue;
1107:             }
1108: 
1109:             if ("NMTOKENS" == ainfo.type) {
1110:                 StringTokenizer tokens = new StringTokenizer (value, " ");
1111: 
1112:                 if (!tokens.hasMoreTokens ()) {
1113:                     
1114:                     error ("NMTOKENS attribute " + aname
1115:                         + " must have at least one name token");
1116:                 } else do {
1117:                     String token = tokens.nextToken ();
1118: 
1119:                     
1120:                     isNmtoken (token, "NMTOKENS attribute", aname);
1121:                 } while (tokens.hasMoreTokens ());
1122:                 continue;
1123:             }
1124: 
1125:             if ("ENTITY" == ainfo.type) {
1126:                 if (!unparsed.contains (value))
1127:                     
1128:                     error ("Value of attribute '" + aname
1129:                         + "' refers to unparsed entity '" + value
1130:                         + "' which was not declared.");
1131:                 continue;
1132:             }
1133: 
1134:             if ("ENTITIES" == ainfo.type) {
1135:                 StringTokenizer tokens = new StringTokenizer (value, " ");
1136: 
1137:                 if (!tokens.hasMoreTokens ()) {
1138:                     
1139:                     error ("ENTITIES attribute " + aname
1140:                         + " must have at least one name token");
1141:                 } else do {
1142:                     String entity = tokens.nextToken ();
1143: 
1144:                     if (!unparsed.contains (entity))
1145:                         
1146:                         error ("Value of attribute '" + aname
1147:                             + "' refers to unparsed entity '" + entity
1148:                             + "' which was not declared.");
1149:                 } while (tokens.hasMoreTokens ());
1150:                 continue;
1151:             }
1152: 
1153:             
1154:             
1155:             
1156:             if (ainfo.type.charAt (0) == '(' 
1157:                     || ainfo.type.startsWith ("NOTATION ")
1158:                     ) {
1159:                 
1160:                 checkEnumeration (value, ainfo.type, aname);
1161:                 continue;
1162:             }
1163:         }
1164: 
1165:         
1166:         
1167:         
1168:         if (info != null) {
1169:             Hashtable   table = info.attributes;
1170: 
1171:             if (table.size () != 0) {
1172:                 Enumeration     e = table.keys ();
1173: 
1174:                 
1175: 
1176:                 while (e.hasMoreElements ()) {
1177:                     aname = (String) e.nextElement ();
1178:                     ainfo = (AttributeInfo) table.get (aname);
1179: 
1180:                     
1181:                     if ("#REQUIRED" == ainfo.mode
1182:                             && atts.getValue (aname) == null) {
1183:                         
1184:                         error ("Attribute '" + aname + "' must be specified "
1185:                             + "for element type " + qName);
1186:                     }
1187:                 }
1188:             }
1189:         }
1190:         super.startElement (uri, localName, qName, atts);
1191:     }
1192: 
1193:     
1198:     public void characters (char ch [], int start, int length)
1199:     throws SAXException
1200:     {
1201:         Recognizer state;
1202: 
1203:         if (contentStack.empty ())
1204:             state = null;
1205:         else
1206:             state = (Recognizer) contentStack.peek ();
1207: 
1208:         
1209:         
1210:         
1211: 
1212:         if (state != null && !state.acceptCharacters ())
1213:             
1214:             error ("Character content not allowed in element "
1215:                 + state.type.name);
1216: 
1217:         super.characters (ch, start, length);
1218:     }
1219: 
1220: 
1221:     
1227:     public void endElement (String uri, String localName, String qName)
1228:     throws SAXException
1229:     {
1230:         try {
1231:             Recognizer state = (Recognizer) contentStack.pop ();
1232: 
1233:             if (state != null && !state.completed ())
1234:                 
1235:                 error ("Premature end for element '"
1236:                     + state.type.name
1237:                     + "', content model "
1238:                     + state.type.model);
1239: 
1240:             
1241:             
1242: 
1243:         } catch (EmptyStackException e) {
1244:             fatalError ("endElement without startElement: " + qName
1245:                 + ((uri == null)
1246:                     ? ""
1247:                     : ( " { '" + uri + "', " + localName + " }")));
1248:         }
1249:         super.endElement (uri, localName, qName);
1250:     }
1251: 
1252:     
1259:     public void endDocument ()
1260:     throws SAXException
1261:     {
1262:         for (Enumeration idNames = ids.keys ();
1263:                 idNames.hasMoreElements ();
1264:                 ) {
1265:             String id = (String) idNames.nextElement ();
1266: 
1267:             if (Boolean.FALSE == ids.get (id)) {
1268:                 
1269:                 error ("Undeclared ID value '" + id
1270:                     + "' was referred to by an IDREF/IDREFS attribute");
1271:             }
1272:         }
1273: 
1274:         resetState ();
1275:         super.endDocument ();
1276:     }
1277: 
1278: 
1279:     
1280:     static private final class ElementInfo
1281:     {
1282:         String                  name;
1283:         String                  model;
1284: 
1285:         
1286:         Hashtable               attributes = new Hashtable (11);
1287: 
1288:         ElementInfo (String n) { name = n; }
1289: 
1290:         private Recognizer      recognizer;
1291: 
1292:         
1293:         
1294:         
1295:         Recognizer      getRecognizer (ValidationConsumer consumer)
1296:         throws SAXException
1297:         {
1298:             if (recognizer == null) {
1299:                 if ("ANY".equals (model))
1300:                     recognizer = ANY;
1301:                 else if ("EMPTY".equals (model))
1302:                     recognizer = new EmptyRecognizer (this);
1303:                 else if ('#' == model.charAt (1))
1304:                     
1305:                     recognizer = new MixedRecognizer (this, consumer);
1306:                 else
1307:                     recognizer = new ChildrenRecognizer (this, consumer);
1308:             }
1309:             return recognizer;
1310:         }
1311:     }
1312: 
1313:     
1314:     static private final class AttributeInfo
1315:     {
1316:         String  type;
1317:         String  mode;           
1318:         String  value;          
1319:     }
1320: 
1321: 
1322:     
1323:     
1324:     
1325: 
1326:     static private final Recognizer     ANY = new Recognizer (null);
1327: 
1328: 
1329:     
1330:     
1331:     static private class Recognizer
1332:     {
1333:         final ElementInfo       type;
1334: 
1335:         Recognizer (ElementInfo t) { type = t; }
1336: 
1337:         
1338:         boolean acceptCharacters ()
1339:         throws SAXException
1340:             
1341:             { return true; }
1342: 
1343:         
1344:         
1345:         
1346:         Recognizer acceptElement (String name)
1347:         throws SAXException
1348:             
1349:             { return this; }
1350: 
1351:         
1352:         boolean completed ()
1353:         throws SAXException
1354:             
1355:             { return true; }
1356: 
1357:         public String toString ()
1358:             
1359:             { return (type == null) ? "ANY" : type.model; }
1360:     }
1361: 
1362:     
1363:     private static final class EmptyRecognizer extends Recognizer
1364:     {
1365:         public EmptyRecognizer (ElementInfo type)
1366:             { super (type); }
1367: 
1368:         
1369:         boolean acceptCharacters ()
1370:             { return false; }
1371: 
1372:         
1373:         Recognizer acceptElement (String name)
1374:             { return null; }
1375:     }
1376: 
1377:     
1378:     private static final class MixedRecognizer extends Recognizer
1379:     {
1380:         private String  permitted [];
1381: 
1382:         
1383:         public MixedRecognizer (ElementInfo t, ValidationConsumer v)
1384:         throws SAXException
1385:         {
1386:             super (t);
1387: 
1388:             
1389:             
1390:             StringTokenizer     tokens = new StringTokenizer (
1391:                 t.model.substring (8, t.model.lastIndexOf (')')),
1392:                 "|");
1393:             Vector              vec = new Vector ();
1394: 
1395:             while (tokens.hasMoreTokens ()) {
1396:                 String token = tokens.nextToken ();
1397: 
1398:                 if (vec.contains (token))
1399:                     v.error ("element " + token
1400:                         + " is repeated in mixed content model: "
1401:                         + t.model);
1402:                 else
1403:                     vec.addElement (token.intern ());
1404:             }
1405:             permitted = new String [vec.size ()];
1406:             for (int i = 0; i < permitted.length; i++)
1407:                 permitted [i] = (String) vec.elementAt (i);
1408: 
1409:             
1410:             
1411:             
1412:             
1413:         }
1414: 
1415:         
1416:         Recognizer acceptElement (String name)
1417:         {
1418:             int         length = permitted.length;
1419: 
1420:             
1421:             
1422:             for (int i = 0; i < length; i++)
1423:                 if (permitted [i] == name)
1424:                     return this;
1425:             
1426:             for (int i = 0; i < length; i++)
1427:                 if (permitted [i].equals (name))
1428:                     return this;
1429:             return null;
1430:         }
1431:     }
1432: 
1433: 
1434:     
1435:     private static final int            F_LOOPHEAD = 0x01;
1436:     private static final int            F_LOOPNEXT = 0x02;
1437: 
1438:     
1439:     private static int                  nodeCount;
1440: 
1441:     
1465:     private static final class ChildrenRecognizer extends Recognizer
1466:         implements Cloneable
1467:     {
1468:         
1469:         
1470:         
1471:         private ValidationConsumer      consumer;
1472: 
1473:         
1474:         
1475:         
1476:         private Recognizer              components [];
1477: 
1478:         
1479:         
1480:         private String                  name;
1481:         private Recognizer              next;
1482: 
1483:         
1484:         
1485:         
1486:         
1487:         private int                     flags;
1488: 
1489: 
1490:         
1491:         private void copyIn (ChildrenRecognizer node)
1492:         {
1493:             
1494:             components = node.components;
1495:             name = node.name;
1496:             next = node.next;
1497:             flags = node.flags;
1498:         }
1499: 
1500:         
1501:         public ChildrenRecognizer (ElementInfo type, ValidationConsumer vc)
1502:         {
1503:             this (vc, type);
1504:             populate (type.model.toCharArray (), 0);
1505:             patchNext (new EmptyRecognizer (type), null);
1506:         }
1507: 
1508:         
1509:         private ChildrenRecognizer (ValidationConsumer vc, ElementInfo type)
1510:         {
1511:             super (type);
1512:             consumer = vc;
1513:         }
1514: 
1515: 
1516:         
1517:         
1518:         
1519:         
1520:         private ChildrenRecognizer shallowClone ()
1521:         {
1522:             try {
1523:                 return (ChildrenRecognizer) clone ();
1524:             } catch (CloneNotSupportedException e) {
1525:                 throw new Error ("clone");
1526:             }
1527:         }
1528: 
1529:         private ChildrenRecognizer deepClone ()
1530:         {
1531:             return deepClone (new Hashtable (37));
1532:         }
1533: 
1534:         private ChildrenRecognizer deepClone (Hashtable table)
1535:         {
1536:             ChildrenRecognizer retval;
1537: 
1538:             if ((flags & F_LOOPHEAD) != 0) {
1539:                 retval = (ChildrenRecognizer) table.get (this);
1540:                 if (retval != null)
1541:                     return this;
1542: 
1543:                 retval = shallowClone ();
1544:                 table.put (this, retval);
1545:             } else
1546:                 retval = shallowClone ();
1547: 
1548:             if (next != null) {
1549:                 if (next instanceof ChildrenRecognizer)
1550:                     retval.next = ((ChildrenRecognizer)next)
1551:                             .deepClone (table);
1552:                 else if (!(next instanceof EmptyRecognizer))
1553:                     throw new RuntimeException ("deepClone");
1554:             }
1555: 
1556:             if (components != null) {
1557:                 retval.components = new Recognizer [components.length];
1558:                 for (int i = 0; i < components.length; i++) {
1559:                     Recognizer temp = components [i];
1560: 
1561:                     if (temp == null)
1562:                         retval.components [i] = null;
1563:                     else if (temp instanceof ChildrenRecognizer)
1564:                         retval.components [i] = ((ChildrenRecognizer)temp)
1565:                                 .deepClone (table);
1566:                     else if (!(temp instanceof EmptyRecognizer))
1567:                         throw new RuntimeException ("deepClone");
1568:                 }
1569:             }
1570: 
1571:             return retval;
1572:         }
1573: 
1574:         
1575:         private void patchNext (Recognizer theNext, Hashtable table)
1576:         {
1577:             
1578:             if ((flags & F_LOOPNEXT) != 0)
1579:                 return;
1580: 
1581:             
1582:             
1583:             if (table != null && table.get (this) != null)
1584:                 return;
1585:             if (table == null)
1586:                 table = new Hashtable ();
1587: 
1588:             
1589:             if (name != null) {
1590:                 if (next == null)
1591:                     next = theNext;
1592:                 else if (next instanceof ChildrenRecognizer) {
1593:                     ((ChildrenRecognizer)next).patchNext (theNext, table);
1594:                 } else if (!(next instanceof EmptyRecognizer))
1595:                     throw new RuntimeException ("patchNext");
1596:                 return;
1597:             }
1598: 
1599:             
1600:             for (int i = 0; i < components.length; i++) {
1601:                 if (components [i] == null)
1602:                     components [i] = theNext;
1603:                 else if (components [i] instanceof ChildrenRecognizer) {
1604:                     ((ChildrenRecognizer)components [i])
1605:                             .patchNext (theNext, table);
1606:                 } else if (!(components [i] instanceof EmptyRecognizer))
1607:                     throw new RuntimeException ("patchNext");
1608:             }
1609: 
1610:             if (table != null && (flags & F_LOOPHEAD) != 0)
1611:                 table.put (this, this);
1612:         }
1613: 
1614:         
1620:         private int populate (char parseBuf [], int startPos)
1621:         {
1622:             int         nextPos = startPos + 1;
1623:             char        c;
1624: 
1625:             if (nextPos < 0 || nextPos >= parseBuf.length)
1626:                 throw new IndexOutOfBoundsException ();
1627: 
1628:             
1629:             
1630: 
1631:             
1632:             
1633:             
1634:             
1635: 
1636:             
1637:             
1638:             if (parseBuf [startPos] != '(') {
1639:                 boolean         done = false;
1640:                 do {
1641:                     switch (c = parseBuf [nextPos]) {
1642:                         case '?': case '*': case '+':
1643:                         case '|': case ',':
1644:                         case  ')':
1645:                             done = true;
1646:                             continue;
1647:                         default:
1648:                             nextPos++;
1649:                             continue;
1650:                     }
1651:                 } while (!done);
1652:                 name = new String (parseBuf, startPos, nextPos - startPos);
1653: 
1654:             
1655:             
1656:             
1657:             } else {
1658:                 
1659:                 
1660:                 ChildrenRecognizer      first;
1661: 
1662:                 first = new ChildrenRecognizer (consumer, type);
1663:                 nextPos = first.populate (parseBuf, nextPos);
1664:                 c = parseBuf [nextPos++];
1665: 
1666:                 if (c == ',' || c == '|') {
1667:                     ChildrenRecognizer  current = first;
1668:                     char                separator = c;
1669:                     Vector              v = null;
1670: 
1671:                     if (separator == '|') {
1672:                         v = new Vector ();
1673:                         v.addElement (first);
1674:                     }
1675: 
1676:                     do {
1677:                         ChildrenRecognizer link;
1678: 
1679:                         link = new ChildrenRecognizer (consumer, type);
1680:                         nextPos = link.populate (parseBuf, nextPos);
1681: 
1682:                         if (separator == ',') {
1683:                             current.patchNext (link, null);
1684:                             current = link;
1685:                         } else
1686:                             v.addElement (link);
1687: 
1688:                         c = parseBuf [nextPos++];
1689:                     } while (c == separator);
1690: 
1691:                     
1692:                     if (separator == '|') {
1693:                         
1694:                         components = new Recognizer [v.size ()];
1695:                         for (int i = 0; i < components.length; i++) {
1696:                             components [i] = (Recognizer)
1697:                                     v.elementAt (i);
1698:                         }
1699:                         
1700: 
1701:                     
1702:                     } else
1703:                         copyIn (first);
1704: 
1705:                 
1706:                 } else
1707:                     copyIn (first);
1708: 
1709:                 if (c !=  ')')
1710:                     throw new RuntimeException ("corrupt content model");
1711:             }
1712: 
1713:             
1714:             
1715:             
1716:             
1717:             
1718:             
1719:             
1720:             if (nextPos < parseBuf.length) {
1721:                 c = parseBuf [nextPos];
1722:                 if (c == '?' || c == '*' || c == '+') {
1723:                     nextPos++;
1724: 
1725:                     
1726:                     
1727:                     
1728:                     
1729:                     if (c == '?') {
1730:                         Recognizer              once = shallowClone ();
1731: 
1732:                         components = new Recognizer [2];
1733:                         components [0] = once;
1734:                         
1735:                         name = null;
1736:                         next = null;
1737:                         flags = 0;
1738: 
1739: 
1740:                     
1741:                     
1742:                     
1743:                     
1744:                     } else if (c == '*') {
1745:                         ChildrenRecognizer      loop = shallowClone ();
1746: 
1747:                         loop.patchNext (this, null);
1748:                         loop.flags |= F_LOOPNEXT;
1749:                         flags = F_LOOPHEAD;
1750: 
1751:                         components = new Recognizer [2];
1752:                         components [0] = loop;
1753:                         
1754:                         name = null;
1755:                         next = null;
1756: 
1757: 
1758:                     
1759:                     
1760:                     
1761:                     
1762:                     
1763:                     
1764:                     
1765:                     } else if (c == '+') {
1766:                         ChildrenRecognizer loop = deepClone ();
1767:                         ChildrenRecognizer choice;
1768: 
1769:                         choice = new ChildrenRecognizer (consumer, type);
1770:                         loop.patchNext (choice, null);
1771:                         loop.flags |= F_LOOPNEXT;
1772:                         choice.flags = F_LOOPHEAD;
1773: 
1774:                         choice.components = new Recognizer [2];
1775:                         choice.components [0] = loop;
1776:                         
1777:                         
1778: 
1779:                         patchNext (choice, null);
1780:                     }
1781:                 }
1782:             }
1783: 
1784:             return nextPos;
1785:         }
1786: 
1787:         
1788:         boolean acceptCharacters ()
1789:             { return false; }
1790: 
1791:         
1792:         Recognizer acceptElement (String type)
1793:         throws SAXException
1794:         {
1795:             
1796:             if (name != null) {
1797:                 if (name.equals (type))
1798:                     return next;
1799:                 return null;
1800:             }
1801: 
1802:             
1803:             
1804:             
1805:             Recognizer  retval = null;
1806: 
1807:             for (int i = 0; i < components.length; i++) {
1808:                 Recognizer temp = components [i].acceptElement (type);
1809: 
1810:                 if (temp == null)
1811:                     continue;
1812:                 else if (!warnNonDeterministic)
1813:                     return temp;
1814:                 else if (retval == null)
1815:                     retval = temp;
1816:                 else if (retval != temp)
1817:                     consumer.error ("Content model " + this.type.model
1818:                         + " is non-deterministic for " + type);
1819:             }
1820:             return retval;
1821:         }
1822: 
1823:         
1824:         boolean completed ()
1825:         throws SAXException
1826:         {
1827:             
1828:             if (name != null)
1829:                 return false;
1830: 
1831:             
1832:             for (int i = 0; i < components.length; i++) {
1833:                 if (components [i].completed ())
1834:                     return true;
1835:             }
1836: 
1837:             return false;
1838:         }
1839: 
1840: 
1927:     }
1928: }