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: }