0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013: package org.netbeans.editor.ext.html.dtd;
0014:
0015: import java.io.IOException;
0016: import java.io.PushbackReader;
0017: import java.io.Reader;
0018: import java.util.ArrayList;
0019: import java.util.Arrays;
0020: import java.util.Comparator;
0021: import java.util.HashMap;
0022: import java.util.HashSet;
0023: import java.util.Iterator;
0024: import java.util.List;
0025: import java.util.Map;
0026: import java.util.Set;
0027: import java.util.SortedMap;
0028: import java.util.TreeMap;
0029: import java.util.TreeSet;
0030:
0031: import org.netbeans.editor.ext.html.WeakHashSet;
0032:
0033: /**
0034: * !!! Includes !!!! String->DTD.Element
0035: *
0036: * @author Petr Nejedly
0037: * @version 0.2
0038: */
0039: class DTDParser extends Object {
0040:
0041: // The provider used to provide the Readers for this DTD.
0042: private ReaderProvider provider = null;
0043:
0044: // Asks for Reader for given DTD.
0045: private Reader getReader(String identifier, String fileName) {
0046: if (provider == null)
0047: return null;
0048: return provider.getReaderForIdentifier(identifier, fileName);
0049: }
0050:
0051: /**
0052: * Weak set for holding already created strings, to not create more
0053: * instances of the same string
0054: */
0055: private WeakHashSet stringCache = new WeakHashSet(131, 0.75f);
0056:
0057: /** Weak set of attributes - helps sharing common attributes */
0058: private WeakHashSet attributes = new WeakHashSet(23, 0.75f);
0059:
0060: /** Weak set of models */
0061: private WeakHashSet models = new WeakHashSet(131, 0.75f);
0062:
0063: /** Weak set of Contents */
0064: private WeakHashSet contents = new WeakHashSet(131, 0.75f);
0065:
0066: /**
0067: * Temporal storage of all ContentLeafs that needs to get their elements
0068: * filled in at the end of parsing
0069: */
0070: Set leafs = new HashSet(131, 0.75f);
0071:
0072: /**
0073: * Map of all character references. Mapping is String name -> DTD.CharRef
0074: * instance
0075: */
0076: private SortedMap charRefs = new TreeMap();
0077:
0078: /**
0079: * Map holding partially completed instances of Element. Mapping is String
0080: * name -> DTD.Element instance
0081: */
0082: private SortedMap elementMap = new TreeMap();
0083:
0084: /**
0085: * Map holding entities during creation of DTD. Mapping is String name ->
0086: * String content. This map should not be used for direct put(..), because
0087: * entities are defined by first declaration and can not be overriden.
0088: */
0089: private Map entityMap = new HashMap();
0090:
0091: public DTD createDTD(ReaderProvider provider, String identifier,
0092: String fileName) throws WrongDTDException {
0093: this .provider = provider;
0094:
0095: Reader reader = getReader(identifier, fileName);
0096: if (reader == null)
0097: throw new WrongDTDException(
0098: "Can't open Reader for public identifier "
0099: + identifier);
0100: try {
0101: parseDTD(new PushbackReader(reader, 1024 * 128));
0102: } catch (IOException e) {
0103: throw new WrongDTDException("IOException during parsing: "
0104: + e.getMessage());
0105: }
0106:
0107: // fixup includes and excludes of all elements
0108: for (Iterator it = elementMap.values().iterator(); it.hasNext();) {
0109: DTD.Element elem = (DTD.Element) it.next();
0110: ContentModelImpl cm = (ContentModelImpl) elem
0111: .getContentModel();
0112:
0113: Set newIncs = new HashSet();
0114: for (Iterator incIter = cm.included.iterator(); incIter
0115: .hasNext();) {
0116: Object oldElem;
0117: Object subElem = oldElem = incIter.next();
0118: if (subElem instanceof String) {
0119: subElem = elementMap.get(((String) subElem)
0120: .toUpperCase());
0121: }
0122: if (subElem == null) {
0123: throw new WrongDTDException("'" + oldElem
0124: + "' element referenced from "
0125: + elem.getName()
0126: + " not found throughout the DTD.");
0127: }
0128: newIncs.add(subElem);
0129: }
0130: cm.included = newIncs;
0131:
0132: Set newExcs = new HashSet();
0133: for (Iterator excIter = cm.excluded.iterator(); excIter
0134: .hasNext();) {
0135: Object oldElem;
0136: Object subElem = oldElem = excIter.next();
0137: if (subElem instanceof String) {
0138: subElem = elementMap.get(((String) subElem)
0139: .toUpperCase());
0140: }
0141: if (subElem == null) {
0142: throw new WrongDTDException("'" + oldElem
0143: + "' element referenced from "
0144: + elem.getName()
0145: + " not found throughout the DTD.");
0146: }
0147: newExcs.add(subElem);
0148: }
0149: cm.excluded = newExcs;
0150: cm.hashcode = cm.content.hashCode() + 2
0151: * cm.included.hashCode() + 3
0152: * cm.excluded.hashCode();
0153: }
0154:
0155: // fixup content leafs
0156: for (Iterator it = leafs.iterator(); it.hasNext();) {
0157: ContentLeafImpl leaf = (ContentLeafImpl) it.next();
0158: leaf.elem = (DTD.Element) elementMap.get(leaf.elemName);
0159: }
0160:
0161: return new DTDImpl(identifier, elementMap, charRefs);
0162: }
0163:
0164: /**
0165: * Method for adding new entities to their map. Obeys the rule that entity,
0166: * once defined, can not be overriden
0167: */
0168: void addEntity(String name, String content) {
0169: if (entityMap.get(name) == null)
0170: entityMap.put(name, content);
0171: }
0172:
0173: /**
0174: * Method for adding new entities to their map. Obeys the rule that entity,
0175: * once defined, can not be overriden
0176: */
0177: void addPublicEntity(String name, String identifier, String file)
0178: throws WrongDTDException {
0179: if (entityMap.get(name) == null) {
0180:
0181: StringBuffer sb = new StringBuffer();
0182: char[] buffer = new char[16384];
0183: Reader r = getReader(identifier, file);
0184: try {
0185: int len;
0186: while ((len = r.read(buffer)) >= 0) {
0187: sb.append(buffer, 0, len);
0188: }
0189: } catch (IOException e) {
0190: throw new WrongDTDException(
0191: "Error reading included public entity " + name
0192: + " - " + e.getMessage());
0193: }
0194:
0195: entityMap.put(name, sb.toString());
0196: }
0197: }
0198:
0199: DTD.Value createValue(String name) {
0200: return new ValueImpl((String) stringCache.put(name));
0201: }
0202:
0203: /** Creates new or lookups old ContentModel with given properites */
0204: DTD.ContentModel createContentModel(DTD.Content content,
0205: Set included, Set excluded) {
0206:
0207: DTD.ContentModel cm = new ContentModelImpl(content, included,
0208: excluded);
0209: return (DTD.ContentModel) models.put(cm);
0210: }
0211:
0212: /** Creates new or lookups old ContentLeaf with given properites */
0213: DTD.Content createContentLeaf(String name) {
0214: DTD.Content c = new ContentLeafImpl(name);
0215: c = (DTD.Content) contents.put(c);
0216: leafs.add(c); // remember for final fixup
0217: return c;
0218: }
0219:
0220: /** Creates new or lookups old ContentNode with given properites */
0221: DTD.Content createContentNode(char type, DTD.Content subContent) {
0222: return (DTD.Content) contents.put(new UnaryContentNodeImpl(
0223: type, subContent));
0224: }
0225:
0226: /** Creates new or lookups old ContentNode with given properites */
0227: DTD.Content createContentNode(char type, DTD.Content[] subContent) {
0228: return (DTD.Content) contents.put(new MultiContentNodeImpl(
0229: type, subContent));
0230: }
0231:
0232: DTD.Element createElement(String name, DTD.ContentModel cm,
0233: boolean optStart, boolean optEnd) {
0234: DTD.Element retVal = new ElementImpl(name, cm, optStart,
0235: optEnd, new TreeMap());
0236: return retVal;
0237: }
0238:
0239: /** Creates new or lookups old attribute with given properites */
0240: DTD.Attribute createAttribute(String name, int type,
0241: String baseType, String typeHelper, String defaultMode,
0242: SortedMap values) {
0243: DTD.Attribute attr = new AttributeImpl(name, type,
0244: (String) stringCache.put(baseType),
0245: (String) stringCache.put(typeHelper),
0246: (String) stringCache.put(defaultMode), values);
0247: return (DTD.Attribute) attributes.put(attr);
0248: }
0249:
0250: /** Adds given instance of DTD.Attribute to Element named elemName */
0251: void addAttrToElement(String elemName, DTD.Attribute attr)
0252: throws WrongDTDException {
0253: ElementImpl elem = (ElementImpl) elementMap.get(elemName
0254: .toUpperCase());
0255: if (elem == null)
0256: throw new WrongDTDException(
0257: "Attribute definition for unknown Element \""
0258: + elemName + "\".");
0259: elem.addAttribute(attr);
0260: }
0261:
0262: void createAddCharRef(String name, char value) {
0263: DTD.CharRef ref = new CharRefImpl(name, value);
0264: charRefs.put(name, ref);
0265: }
0266:
0267: private boolean isNameChar(char c) {
0268: return Character.isLetterOrDigit(c) || c == '_' || c == '-'
0269: || c == '.' || c == ':';
0270: }
0271:
0272: /*----------------------------------------------------------------------------*/
0273: /*----------------------------- Parsing routines ---------------------------- */
0274: /*----------------------------------------------------------------------------*/
0275: private static final int DTD_INIT = 0;
0276: private static final int DTD_LT = 1; // after '<'
0277: private static final int DTD_EXC = 2; // after "<!"
0278: private static final int DTD_MINUS = 3; // after "<!-"
0279: private static final int DTD_ACOMMENT = 4; // after comment was parsed,
0280:
0281: // awaiting '>'
0282:
0283: private void parseDTD(PushbackReader in) throws IOException,
0284: WrongDTDException {
0285: int state = DTD_INIT;
0286: for (;;) {
0287: int i = in.read();
0288: if (i == -1) {
0289: break;
0290: }
0291: switch (state) {
0292: case DTD_INIT:
0293: switch (i) {
0294: case '<':
0295: state = DTD_LT;
0296: break;
0297: case '%':
0298: parseEntityReference(in);
0299: break; // Stay in DTD_INIT
0300: }
0301: break;
0302:
0303: case DTD_LT:
0304: if (i != '!')
0305: throw new WrongDTDException("Unexpected char '"
0306: + (char) i + "' after '<'");
0307: state = DTD_EXC;
0308: break;
0309:
0310: case DTD_EXC:
0311: switch (i) {
0312: case '-':
0313: state = DTD_MINUS;
0314: break;
0315: case '[':
0316: parseOptional(in);
0317: state = DTD_INIT;
0318: break;
0319: default:
0320: in.unread(i);
0321: parseMarkup(in);
0322: state = DTD_INIT;
0323: break;
0324: }
0325: break;
0326:
0327: case DTD_MINUS:
0328: if (i != '-')
0329: throw new WrongDTDException("Unexpected char '"
0330: + (char) i + "' after \"<!-\"");
0331: parseComment(in);
0332: state = DTD_ACOMMENT;
0333: break;
0334:
0335: case DTD_ACOMMENT:
0336: if (i != '>')
0337: throw new WrongDTDException("Unexpected char '"
0338: + (char) i + "' after comment");
0339: state = DTD_INIT;
0340: break;
0341:
0342: }
0343: }
0344: if (state != DTD_INIT)
0345: throw new WrongDTDException("Premature end of DTD"); // NOI18N
0346: }
0347:
0348: /**
0349: * Parser that reads the markup type after <!. Recognizes ENTITY, ELEMENT
0350: * and ATTLIST markup and forwards their processing to proper parser. It
0351: * gets the control just after starting "<!" and releases after eating
0352: * final '>'
0353: */
0354: private void parseMarkup(PushbackReader in) throws IOException,
0355: WrongDTDException {
0356: StringBuffer sb = new StringBuffer();
0357: for (;;) {
0358: int i = in.read();
0359: if (i == -1)
0360: throw new WrongDTDException("Premature end of DTD"); // NOI18N
0361: // EOF
0362: if (i == ' ')
0363: break;
0364: sb.append((char) i); // next char of name
0365: }
0366:
0367: String markup = sb.toString();
0368:
0369: if ("ENTITY".equals(markup)) {
0370: parseEntityDefinition(in);
0371: } else if ("ELEMENT".equals(markup)) {
0372: parseElement(in);
0373: } else if ("ATTLIST".equals(markup)) {
0374: parseAttlist(in);
0375: } else
0376: throw new WrongDTDException("Wrong DTD markup <!" + markup);
0377: }
0378:
0379: private static final int PED_INIT = 0;
0380: private static final int PED_PERCENT = 1;
0381: private static final int PED_CHAR = 2;
0382: private static final int PED_NAME = 3;
0383: private static final int PED_ANAME = 4;
0384: private static final int PED_VAL = 5;
0385: private static final int PED_TYPE = 6;
0386: private static final int PED_AVAL = 7;
0387: private static final int PED_AVAL_M = 8;
0388: private static final int PED_ATYPE = 9;
0389: private static final int PED_ID = 10;
0390: private static final int PED_AID = 11;
0391: private static final int PED_FILE = 12;
0392: private static final int PED_AFILE = 13;
0393: private static final int PED_AFILE_M = 14;
0394: private static final int PED_ACHAR = 15;
0395: private static final int PED_CH_TYPE = 16;
0396: private static final int PED_CH_ATYPE = 17;
0397: private static final int PED_CH_QUOT = 18;
0398:
0399: /* TODO: Parsing fo character references */
0400: private void parseEntityDefinition(PushbackReader in)
0401: throws IOException, WrongDTDException {
0402: int state = PED_INIT;
0403: StringBuffer name = new StringBuffer();
0404: StringBuffer value = new StringBuffer();
0405: StringBuffer type = new StringBuffer();
0406: StringBuffer identifier = new StringBuffer();
0407:
0408: for (;;) {
0409: int i = in.read();
0410: if (i == -1)
0411: throw new WrongDTDException("Premature end of DTD"); // NOI18N
0412: // EOF
0413: switch (state) {
0414: case PED_INIT:
0415: if (Character.isWhitespace((char) i))
0416: break;
0417: if (i == '%')
0418: state = PED_PERCENT;
0419: else {
0420: name.append((char) i);
0421: state = PED_CHAR;
0422: }
0423: break;
0424:
0425: case PED_PERCENT:
0426: if (Character.isWhitespace((char) i))
0427: break;
0428: name.append((char) i);
0429: state = PED_NAME;
0430: break;
0431:
0432: case PED_NAME:
0433: if (Character.isWhitespace((char) i)) {
0434: state = PED_ANAME;
0435: } else {
0436: name.append((char) i);
0437: }
0438: break;
0439:
0440: case PED_ANAME:
0441: if (Character.isWhitespace((char) i))
0442: break;
0443: if (i == '"')
0444: state = PED_VAL;
0445: else {
0446: in.unread(i);
0447: state = PED_TYPE;
0448: }
0449: break;
0450:
0451: case PED_VAL:
0452: if (i == '"') {
0453: addEntity(name.toString(), value.toString());
0454: state = PED_AVAL;
0455: } else {
0456: value.append((char) i);
0457: }
0458: break;
0459:
0460: case PED_AVAL:
0461: if (i == '>') {
0462: return;
0463: }
0464: if (i == '-')
0465: state = PED_AVAL_M;
0466: break;
0467:
0468: case PED_AVAL_M:
0469: if (i == '-')
0470: parseComment(in);
0471: state = PED_AVAL;
0472: break;
0473:
0474: case PED_TYPE:
0475: if (Character.isWhitespace((char) i)) {
0476: if (type.toString().equals("PUBLIC")) {
0477: state = PED_ATYPE;
0478: } else {
0479: throw new WrongDTDException(
0480: "Unexpected entity type \"" + type
0481: + "\".");
0482: }
0483: } else {
0484: type.append((char) i);
0485: }
0486: break;
0487:
0488: case PED_ATYPE:
0489: if (Character.isWhitespace((char) i))
0490: break;
0491: if (i == '"') {
0492: state = PED_ID;
0493: break;
0494: }
0495: throw new WrongDTDException("Unexpected char '"
0496: + (char) i + "' in PUBLIC entity.");
0497:
0498: case PED_ID:
0499: if (i == '"') {
0500: state = PED_AID;
0501: } else {
0502: identifier.append((char) i);
0503: }
0504: break;
0505:
0506: case PED_AID:
0507: if (Character.isWhitespace((char) i))
0508: break;
0509: if (i == '"') {
0510: state = PED_FILE;
0511: break;
0512: }
0513: if (i == '>') {
0514: addPublicEntity(name.toString(), identifier
0515: .toString(), null);
0516: return;
0517: }
0518: throw new WrongDTDException("Unexpected char '"
0519: + (char) i + "' in PUBLIC entity.");
0520:
0521: case PED_FILE:
0522: if (i == '"') {
0523: state = PED_AFILE;
0524: } else {
0525: value.append((char) i);
0526: }
0527: break;
0528:
0529: case PED_AFILE:
0530: if (Character.isWhitespace((char) i))
0531: break;
0532: if (i == '-') {
0533: state = PED_AFILE_M;
0534: break;
0535: }
0536: if (i == '>') {
0537: addPublicEntity(name.toString(), identifier
0538: .toString(), value.toString());
0539: return;
0540: }
0541: throw new WrongDTDException("Unexpected char '"
0542: + (char) i + "' in PUBLIC entity.");
0543:
0544: case PED_AFILE_M:
0545: if (i == '-') {
0546: parseComment(in);
0547: state = PED_FILE;
0548: break;
0549: }
0550: throw new WrongDTDException("Unexpected sequence \"-"
0551: + (char) i + "\" in in PUBLIC entity.");
0552:
0553: case PED_CHAR:
0554: if (Character.isWhitespace((char) i)) {
0555: state = PED_ACHAR;
0556: } else {
0557: name.append((char) i);
0558: }
0559: break;
0560:
0561: case PED_ACHAR:
0562: if (Character.isWhitespace((char) i))
0563: break;
0564: else {
0565: type.append((char) i);
0566: state = PED_CH_TYPE;
0567: }
0568: break;
0569:
0570: case PED_CH_TYPE:
0571: if (Character.isWhitespace((char) i)) {
0572: if (type.toString().equals("CDATA")) {
0573: state = PED_ATYPE;
0574: state = PED_CH_ATYPE;
0575: } else {
0576: throw new WrongDTDException(
0577: "Unexpected entity type \"" + type
0578: + "\".");
0579: }
0580: } else {
0581: type.append((char) i);
0582: }
0583: break;
0584:
0585: case PED_CH_ATYPE:
0586: if (Character.isWhitespace((char) i))
0587: break;
0588: else if (i == '"') {
0589: state = PED_CH_QUOT;
0590: } else {
0591: throw new WrongDTDException("Unexpected char '"
0592: + (char) i + "' in entity.");
0593: }
0594: break;
0595:
0596: case PED_CH_QUOT:
0597: if (i == '"') {
0598: value.delete(0, 2);
0599: value.deleteCharAt(value.length() - 1);
0600: int code = Integer.parseInt(value.toString());
0601: createAddCharRef(name.toString(), (char) code);
0602: state = PED_AVAL;
0603: } else {
0604: value.append((char) i);
0605: }
0606: }
0607:
0608: }
0609: }
0610:
0611: private static final int GR_INIT = 0;
0612: private static final int GR_NAME = 1;
0613: private static final int GR_ANAME = 2;
0614:
0615: /**
0616: * Parse group of names separated by '|' character and optional spaces
0617: *
0618: * @return List of Strings containing names
0619: */
0620: private List parseGroup(PushbackReader in) throws IOException,
0621: WrongDTDException {
0622: int state = GR_INIT;
0623: StringBuffer name = new StringBuffer();
0624: List list = new ArrayList();
0625:
0626: for (;;) {
0627: int i = in.read();
0628: if (i == -1)
0629: throw new WrongDTDException("Premature end of DTD"); // NOI18N
0630: // EOF
0631: switch (state) {
0632: case GR_INIT:
0633: if (Character.isWhitespace((char) i))
0634: break;
0635: if (i == '%') {
0636: parseEntityReference(in);
0637: } else {
0638: name.append((char) i);
0639: state = GR_NAME;
0640: }
0641: break;
0642:
0643: case GR_NAME:
0644: if (isNameChar((char) i)) {
0645: name.append((char) i);
0646: break;
0647: }
0648: switch (i) {
0649: case ')':
0650: list.add(name.toString());
0651: return list;
0652: case '|':
0653: list.add(name.toString());
0654: name.setLength(0);
0655: state = GR_INIT;
0656: break;
0657: default:
0658: if (Character.isWhitespace((char) i)) {
0659: list.add(name.toString());
0660: name.setLength(0);
0661: state = GR_ANAME;
0662: break;
0663: } else {
0664: throw new WrongDTDException("Unexpected char '"
0665: + (char) i + "' in group definition.");
0666: }
0667: }
0668: break;
0669:
0670: case GR_ANAME:
0671: if (Character.isWhitespace((char) i))
0672: break;
0673: switch (i) {
0674: case ')':
0675: return list;
0676: case '|':
0677: state = GR_INIT;
0678: break;
0679: default:
0680: throw new WrongDTDException("Unexpected char '"
0681: + (char) i + "' in group definition.");
0682: }
0683: break;
0684: }
0685: }
0686:
0687: }
0688:
0689: private static final int EL_INIT = 0;
0690: private static final int EL_NAME = 1;
0691: private static final int EL_ANAME = 2;
0692: private static final int EL_ASTART = 3;
0693: private static final int EL_ACONTENT = 4;
0694: private static final int EL_PLUS = 5;
0695: private static final int EL_MINUS = 6;
0696:
0697: /**
0698: * parse the whole element(s) definition including content model. Create
0699: * corresponding instances of DTD.Element filled with proper informations.
0700: * Make the same content models and their contents shared across the DTD
0701: */
0702: private void parseElement(PushbackReader in) throws IOException,
0703: WrongDTDException {
0704: int state = EL_INIT;
0705: StringBuffer name = new StringBuffer();
0706: List list = null;
0707: boolean optStart = false;
0708: boolean optEnd = false;
0709: DTD.Content content = null;
0710: Set inSet = new HashSet();
0711: Set exSet = new HashSet();
0712:
0713: for (;;) {
0714: int i = in.read();
0715: if (i == -1)
0716: break;
0717: switch (state) {
0718: case EL_INIT:
0719: if (Character.isWhitespace((char) i))
0720: break;
0721: switch (i) {
0722: case '(':
0723: list = parseGroup(in);
0724: state = EL_ANAME;
0725: break;
0726: case '%':
0727: parseEntityReference(in);
0728: break; // Stay in EL_INIT
0729: default:
0730: name.append((char) i);
0731: state = EL_NAME;
0732: break;
0733: }
0734: break;
0735:
0736: case EL_NAME:
0737: if (Character.isWhitespace((char) i)) {
0738: state = EL_ANAME;
0739: list = new ArrayList();
0740: list.add(name.toString());
0741: } else {
0742: name.append((char) i);
0743: }
0744: break;
0745:
0746: case EL_ANAME:
0747: if (Character.isWhitespace((char) i))
0748: break;
0749: switch (i) {
0750: case 'O':
0751: optStart = true; // fall fhrough
0752: case '-':
0753: state = EL_ASTART;
0754: break;
0755: default:
0756: throw new WrongDTDException("Unexpected char '"
0757: + (char) i
0758: + "' in ELEMENT optStart definition.");
0759: }
0760: break;
0761:
0762: case EL_ASTART:
0763: if (Character.isWhitespace((char) i))
0764: break;
0765: switch (i) {
0766: case 'O':
0767: optEnd = true; // fall fhrough
0768: case '-':
0769: content = parseContent(in);
0770: state = EL_ACONTENT;
0771: break;
0772: default:
0773: throw new WrongDTDException("Unexpected char '"
0774: + (char) i
0775: + "' in ELEMENT optEnd definition.");
0776: }
0777: break;
0778:
0779: case EL_ACONTENT:
0780: if (Character.isWhitespace((char) i))
0781: break;
0782: switch (i) {
0783: case '+':
0784: state = EL_PLUS;
0785: break;
0786: case '-':
0787: state = EL_MINUS;
0788: break;
0789: case '>':
0790: DTD.ContentModel cm = createContentModel(content,
0791: inSet, exSet);
0792: for (Iterator iter = list.iterator(); iter
0793: .hasNext();) {
0794: String key = ((String) iter.next())
0795: .toUpperCase();
0796: elementMap.put(key, createElement(key, cm,
0797: optStart, optEnd));
0798: }
0799: return;
0800: default:
0801: throw new WrongDTDException("Unexpected char '"
0802: + (char) i + "' in ELEMENT definition.");
0803: }
0804: break;
0805:
0806: case EL_PLUS:
0807: if (i == '(') {
0808: state = EL_ACONTENT;
0809: inSet.addAll(parseGroup(in));
0810: } else {
0811: throw new WrongDTDException("Unexpected char '"
0812: + (char) i + "' in ELEMENT definition.");
0813: }
0814: break;
0815:
0816: case EL_MINUS:
0817: switch (i) {
0818: case '(':
0819: state = EL_ACONTENT;
0820: List l = parseGroup(in);
0821: exSet.addAll(l);
0822: break;
0823: case '-':
0824: state = EL_ACONTENT;
0825: parseComment(in);
0826: break;
0827: default:
0828: throw new WrongDTDException("Unexpected char '"
0829: + (char) i + "' in ELEMENT definition.");
0830: }
0831: break;
0832: }
0833: }
0834:
0835: // XXX
0836: }
0837:
0838: private static final int CO_INIT = 0;
0839: private static final int CO_NAME = 1;
0840: private static final int CO_AMODEL = 2;
0841: private static final int CO_AND = 3;
0842: private static final int CO_OR = 4;
0843: private static final int CO_SEQ = 5;
0844: private static final int CO_AGROUP = 6;
0845:
0846: /**
0847: * This automata would parse content model definitions and return them as a
0848: * Content instance of root of generated CM tree
0849: */
0850: private DTD.Content parseContent(PushbackReader in)
0851: throws IOException, WrongDTDException {
0852: int state = EL_INIT;
0853: StringBuffer name = new StringBuffer();
0854: ArrayList list = null;
0855: DTD.Content content = null;
0856:
0857: for (;;) {
0858: int i = in.read();
0859: if (i == -1)
0860: break;
0861: switch (state) {
0862: case CO_INIT:
0863: if (Character.isWhitespace((char) i))
0864: break;
0865: switch (i) {
0866: case '%':
0867: parseEntityReference(in);
0868: break; // Stay in CO_INIT
0869: case '(':
0870: content = parseContent(in);
0871: state = CO_AMODEL;
0872: break;
0873: default:
0874: name.append((char) i);
0875: state = CO_NAME;
0876: break;
0877: }
0878: break;
0879:
0880: case CO_NAME:
0881: if (isNameChar((char) i)) {
0882: name.append((char) i);
0883: } else {
0884: switch (i) {
0885: case '?':
0886: case '+':
0887: case '*':
0888: DTD.Content leaf = createContentLeaf(name
0889: .toString());
0890: return createContentNode((char) i, leaf);
0891:
0892: default:
0893: in.unread(i);
0894: return createContentLeaf(name.toString());
0895: }
0896: }
0897: break;
0898:
0899: case CO_AMODEL:
0900: if (Character.isWhitespace((char) i))
0901: break;
0902: switch (i) {
0903: case '&':
0904: list = new ArrayList();
0905: list.add(content);
0906: list.add(parseContent(in));
0907: state = CO_AND;
0908: break;
0909: case '|':
0910: list = new ArrayList();
0911: list.add(content);
0912: list.add(parseContent(in));
0913: state = CO_OR;
0914: break;
0915: case ',':
0916: list = new ArrayList();
0917: list.add(content);
0918: list.add(parseContent(in));
0919: state = CO_SEQ;
0920: break;
0921: case ')':
0922: state = CO_AGROUP;
0923: break;
0924: default:
0925: throw new WrongDTDException("Unexpected char '"
0926: + (char) i
0927: + "' in ELEMENT optEnd definition.");
0928: }
0929: break;
0930:
0931: case CO_AND:
0932: if (Character.isWhitespace((char) i))
0933: break;
0934: switch (i) {
0935: case '&':
0936: list.add(parseContent(in));
0937: break;
0938: case ')':
0939: content = createContentNode('&',
0940: (DTD.Content[]) list
0941: .toArray(new DTD.Content[0]));
0942: state = CO_AGROUP;
0943: break;
0944: default:
0945: throw new WrongDTDException("Unexpected char '"
0946: + (char) i
0947: + "' in ContentModel definition.");
0948: }
0949: break;
0950:
0951: case CO_OR:
0952: if (Character.isWhitespace((char) i))
0953: break;
0954: switch (i) {
0955: case '|':
0956: list.add(parseContent(in));
0957: break;
0958: case ')':
0959: content = createContentNode('|',
0960: (DTD.Content[]) list
0961: .toArray(new DTD.Content[0]));
0962: state = CO_AGROUP;
0963: break;
0964: default:
0965: throw new WrongDTDException("Unexpected char '"
0966: + (char) i
0967: + "' in ContentModel definition.");
0968: }
0969: break;
0970:
0971: case CO_SEQ:
0972: if (Character.isWhitespace((char) i))
0973: break;
0974: switch (i) {
0975: case ',':
0976: list.add(parseContent(in));
0977: break;
0978: case ')':
0979: content = createContentNode(',',
0980: (DTD.Content[]) list
0981: .toArray(new DTD.Content[0]));
0982: state = CO_AGROUP;
0983: break;
0984: default:
0985: throw new WrongDTDException("Unexpected char '"
0986: + (char) i
0987: + "' in ContentModel definition.");
0988: }
0989: break;
0990:
0991: case CO_AGROUP:
0992: if (Character.isWhitespace((char) i))
0993: return content;
0994: switch (i) {
0995: case '?':
0996: case '+':
0997: case '*':
0998: return createContentNode((char) i, content);
0999: default:
1000: in.unread(i);
1001: return content;
1002: }
1003: }
1004: }
1005:
1006: throw new WrongDTDException("Premature end of DTD"); // NOI18N EOF
1007:
1008: }
1009:
1010: private static final int ATT_INIT = 0;
1011: private static final int ATT_NAME = 1;
1012: private static final int ATT_ANAME = 2;
1013: private static final int ATT_ANAME_M = 3;
1014: private static final int ATT_VAR = 4;
1015: private static final int ATT_AVAR = 5;
1016: private static final int ATT_TYPE = 6;
1017: private static final int ATT_ATYPE = 7;
1018: private static final int ATT_MODE = 8;
1019:
1020: private void parseAttlist(PushbackReader in) throws IOException,
1021: WrongDTDException {
1022: int state = ATT_INIT;
1023: StringBuffer name = new StringBuffer();
1024: List list = null; // List of tag names for which are these attribs
1025: StringBuffer attr = new StringBuffer(); // name of attribute
1026: List values = null; // (list of possible values
1027: StringBuffer type = new StringBuffer(); // OR the type of attribute )
1028: String typeHelper = null; // AND name of entity
1029: StringBuffer mode = new StringBuffer(); // default mode of this attrib
1030: for (;;) {
1031: int i = in.read();
1032: if (i == -1)
1033: break;
1034: switch (state) {
1035: case ATT_INIT:
1036: if (Character.isWhitespace((char) i))
1037: break;
1038: switch (i) {
1039: case '%':
1040: parseEntityReference(in);
1041: break; // Stay in ATT_INIT
1042: case '(':
1043: list = parseGroup(in);
1044: state = ATT_ANAME;
1045: break;
1046: default:
1047: name.append((char) i);
1048: state = ATT_NAME;
1049: break;
1050: }
1051: break;
1052:
1053: case ATT_NAME:
1054: if (Character.isWhitespace((char) i)) {
1055: list = new ArrayList();
1056: list.add(name.toString());
1057: state = ATT_ANAME;
1058: break;
1059: }
1060: name.append((char) i);
1061: break;
1062:
1063: case ATT_ANAME:
1064: if (Character.isWhitespace((char) i))
1065: break;
1066: switch (i) {
1067: case '%':
1068: parseEntityReference(in);
1069: break; // Stay in ATT_ANAME
1070: case '-':
1071: state = ATT_ANAME_M;
1072: break;
1073: case '>':
1074: return;
1075: default:
1076: attr.append((char) i);
1077: state = ATT_VAR;
1078: break;
1079: }
1080: break;
1081:
1082: case ATT_ANAME_M:
1083: if (i == '-') {
1084: parseComment(in); // skip the comment
1085: state = ATT_ANAME;
1086: } else {
1087: throw new WrongDTDException("Unexpected char '"
1088: + (char) i + "' in ATTLIST definition.");
1089: }
1090: break;
1091:
1092: case ATT_VAR:
1093: if (Character.isWhitespace((char) i)) {
1094: state = ATT_AVAR;
1095: break;
1096: }
1097: attr.append((char) i);
1098: break;
1099:
1100: case ATT_AVAR:
1101: if (Character.isWhitespace((char) i))
1102: break;
1103: switch (i) {
1104: case '%':
1105: typeHelper = parseEntityReference(in);
1106: break; // Stay in ATT_AVAR
1107: case '(':
1108: values = parseGroup(in);
1109: state = ATT_ATYPE;
1110: break;
1111: default:
1112: type.append((char) i);
1113: state = ATT_TYPE;
1114: break;
1115: }
1116: break;
1117:
1118: case ATT_TYPE:
1119: if (Character.isWhitespace((char) i)) {
1120: state = ATT_ATYPE;
1121: break;
1122: }
1123: type.append((char) i);
1124: break;
1125:
1126: case ATT_ATYPE:
1127: if (Character.isWhitespace((char) i))
1128: break;
1129: switch (i) {
1130: case '%':
1131: parseEntityReference(in);
1132: break; // Stay in ATT_ATYPE
1133: default:
1134: mode.append((char) i);
1135: state = ATT_MODE;
1136: break;
1137: }
1138: break;
1139:
1140: case ATT_MODE:
1141: if (Character.isWhitespace((char) i)) {
1142: // Create attr and add it to all tags
1143: DTD.Attribute a = null;
1144:
1145: if (values == null) { // HOTSPOT for internation of
1146: // strings!!!
1147: a = createAttribute(attr.toString(),
1148: DTD.Attribute.TYPE_BASE, type
1149: .toString(), typeHelper, mode
1150: .toString(), null);
1151: } else if (values.size() == 1) {
1152: a = createAttribute(attr.toString(),
1153: DTD.Attribute.TYPE_BOOLEAN, null,
1154: typeHelper, mode.toString(), null);
1155: } else {
1156: SortedMap vals = new TreeMap();
1157: for (Iterator iter = values.iterator(); iter
1158: .hasNext();) {
1159: String valName = (String) iter.next();
1160: vals.put(valName, createValue(valName));
1161: }
1162: a = createAttribute(attr.toString(),
1163: DTD.Attribute.TYPE_SET, null,
1164: typeHelper, mode.toString(), vals);
1165: }
1166: for (Iterator iter = list.iterator(); iter
1167: .hasNext();) {
1168: addAttrToElement((String) iter.next(), a);
1169: }
1170:
1171: typeHelper = null;
1172: attr.setLength(0);
1173: type.setLength(0);
1174: mode.setLength(0);
1175: values = null;
1176:
1177: state = ATT_ANAME;
1178: break;
1179: }
1180: mode.append((char) i);
1181: break;
1182: }
1183: }
1184: }
1185:
1186: private static final int OPT_INIT = 0;
1187: private static final int OPT_PROCESS = 1;
1188: private static final int OPT_APROCESS = 2;
1189: private static final int OPT_CONTENT = 3;
1190: private static final int OPT_BRAC1 = 4;
1191: private static final int OPT_BRAC2 = 5;
1192:
1193: /**
1194: * Parser that takes care of conditional inclusion/exclusion of part of DTD.
1195: * Gets the control just after "<!["
1196: */
1197: private void parseOptional(PushbackReader in) throws IOException,
1198: WrongDTDException {
1199: int state = OPT_INIT;
1200: StringBuffer process = new StringBuffer();
1201: StringBuffer content = new StringBuffer();
1202: boolean ignore = false;
1203:
1204: for (;;) {
1205: int i = in.read();
1206: if (i == -1)
1207: break; // EOF
1208: switch (state) {
1209: case OPT_INIT:
1210: if (Character.isWhitespace((char) i))
1211: break;
1212: if (i == '%') {
1213: parseEntityReference(in);
1214: break;
1215: }
1216: process.append((char) i);
1217: state = OPT_PROCESS;
1218: break;
1219:
1220: case OPT_PROCESS:
1221: if (Character.isWhitespace((char) i)) {
1222: String s = process.toString();
1223: if ("IGNORE".equals(s))
1224: ignore = true;
1225: else if (!"INCLUDE".equals(s))
1226: throw new WrongDTDException(
1227: "Unexpected processing instruction "
1228: + s);
1229: state = OPT_APROCESS;
1230: } else {
1231: process.append((char) i);
1232: }
1233: break;
1234:
1235: case OPT_APROCESS:
1236: if (Character.isWhitespace((char) i))
1237: break;
1238: if (i == '[')
1239: state = OPT_CONTENT;
1240: else
1241: throw new WrongDTDException("Unexpected char '"
1242: + (char) i + "' in processing instruction.");
1243: break;
1244:
1245: case OPT_CONTENT:
1246: if (i == ']')
1247: state = OPT_BRAC1;
1248: else
1249: content.append((char) i);
1250: break;
1251:
1252: case OPT_BRAC1:
1253: if (i == ']')
1254: state = OPT_BRAC2;
1255: else {
1256: content.append(']').append((char) i);
1257: state = OPT_CONTENT;
1258: }
1259: break;
1260:
1261: case OPT_BRAC2:
1262: if (Character.isWhitespace((char) i))
1263: break;
1264: if (i == '>') {
1265: if (!ignore)
1266: in.unread(content.toString().toCharArray());
1267: return;
1268: }
1269: throw new WrongDTDException("Unexpected char '"
1270: + (char) i + "' in processing instruction.");
1271: }
1272: }
1273:
1274: }
1275:
1276: private static final int COMM_TEXT = 0; // anywhere in text
1277: private static final int COMM_DASH = 1; // after '-'
1278:
1279: /** Parser that eats everything until two consecutive dashes (inclusive) */
1280: private void parseComment(PushbackReader in) throws IOException,
1281: WrongDTDException {
1282: int state = COMM_TEXT;
1283: for (;;) {
1284: int i = in.read();
1285: if (i == -1)
1286: break; // EOF
1287: switch (state) {
1288: case COMM_TEXT:
1289: if (i == '-')
1290: state = COMM_DASH;
1291: break;
1292: case COMM_DASH:
1293: if (i == '-')
1294: return; // finished eating comment
1295: state = COMM_TEXT;
1296: break;
1297: }
1298: }
1299: throw new WrongDTDException("Premature end of DTD"); // NOI18N
1300: }
1301:
1302: /**
1303: * Parser that reads the name of entity reference and replace it with the
1304: * content of that entity (using the pushback capability of input). It gets
1305: * the control just after starting '%'
1306: *
1307: * @returns the name of reference which was replaced.
1308: */
1309: private String parseEntityReference(PushbackReader in)
1310: throws IOException, WrongDTDException {
1311: StringBuffer sb = new StringBuffer();
1312: for (;;) {
1313: int i = in.read();
1314: if (i == -1)
1315: break; // EOF
1316: if (isNameChar((char) i)) {
1317: sb.append((char) i); // next char of name
1318: } else {
1319: String entValue = (String) entityMap.get(sb.toString()); // get
1320: // the
1321: // entity
1322: // content
1323: if (entValue == null)
1324: throw new WrongDTDException("No such entity: \""
1325: + sb + "\"");
1326:
1327: if (i != ';')
1328: in.unread(i);
1329: in.unread(entValue.toCharArray()); // push it back to stream
1330: return sb.toString();
1331: }
1332: }
1333: throw new WrongDTDException("Premature end of DTD"); // NOI18N
1334: }
1335:
1336: public static class WrongDTDException extends Exception {
1337: public WrongDTDException(String reason) {
1338: super (reason);
1339: }
1340: }
1341:
1342: /*----------------------------------------------------------------------------*/
1343: /*---------- Implementation of classes this factory uses as results ----------*/
1344: /*----------------------------------------------------------------------------*/
1345:
1346: /** Implementation of the DTD which this DTDcreator works as factory for. */
1347: private static class DTDImpl implements DTD {
1348: private String id;
1349: private SortedMap elements;
1350: private SortedMap charRefs;
1351:
1352: DTDImpl(String identifier, SortedMap elements,
1353: SortedMap charRefs) {
1354: this .id = identifier;
1355: this .elements = elements;
1356: this .charRefs = charRefs;
1357: }
1358:
1359: /** Identify this instance of DTD */
1360: public String getIdentifier() {
1361: return id;
1362: }
1363:
1364: /** Get List of all Elements whose names starts with given prefix */
1365: public List getElementList(String prefix) {
1366: List l = new ArrayList();
1367: prefix = prefix == null ? "" : prefix.toUpperCase();
1368: Iterator i = elements.tailMap(prefix).entrySet().iterator();
1369:
1370: while (i.hasNext()) {
1371: Map.Entry entry = (Map.Entry) i.next();
1372: if (((String) entry.getKey()).startsWith(prefix)) {
1373: l.add(entry.getValue());
1374: } else { // we're getting data from SortedSet, so when any
1375: break; // entry fails, all remaining entry would fail.
1376: }
1377: }
1378:
1379: return l;
1380: }
1381:
1382: /** Get the Element of given name. */
1383: public DTD.Element getElement(String name) {
1384: return (DTD.Element) elements.get(name);
1385: }
1386:
1387: /** Get List of all CharRefs whose aliases starts with given prefix. */
1388: public List getCharRefList(String prefix) {
1389: List l = new ArrayList();
1390: Iterator i = charRefs.tailMap(prefix).entrySet().iterator();
1391:
1392: while (i.hasNext()) {
1393: Map.Entry entry = (Map.Entry) i.next();
1394: if (((String) entry.getKey()).startsWith(prefix)) {
1395: l.add(entry.getValue());
1396: } else { // we're getting data from SortedSet, so when any
1397: break; // entry fails, all remaining entry would fail.
1398: }
1399: }
1400:
1401: return l;
1402: }
1403:
1404: /** Get the CharRef of given name */
1405: public DTD.CharRef getCharRef(String name) {
1406: return (DTD.CharRef) charRefs.get(name);
1407: }
1408:
1409: public String toString() {
1410: return super .toString() + "[id=" + id + ", elements="
1411: + elements + ",charRefs=" + charRefs + "]"; // NOI18N
1412: }
1413: }
1414:
1415: /** Implementation of Element used by this DTDcreator. */
1416: private static class ElementImpl implements DTD.Element {
1417:
1418: private String name;
1419: private DTD.ContentModel model;
1420: private boolean optStart;
1421: private boolean optEnd;
1422: private SortedMap attributes; // these are sorted just by name
1423: private DTD dtd;
1424:
1425: ElementImpl(String name, DTD.ContentModel model,
1426: boolean optStart, boolean optEnd, SortedMap attributes) {
1427: this .name = name;
1428: this .model = model;
1429: this .optStart = optStart;
1430: this .optEnd = optEnd;
1431: this .attributes = attributes;
1432: }
1433:
1434: /** Get the name of this Element */
1435: public String getName() {
1436: return name;
1437: }
1438:
1439: /** Shorthand to resolving if content model of this Element is EMPTY */
1440: public boolean isEmpty() {
1441: if (optEnd && model.getContent() instanceof DTD.ContentLeaf)
1442: return true;
1443: // && ((DTD.ContentLeaf)model.getContent()).getName().equals(
1444: // "EMPTY" ) ) return true;
1445: return false;
1446: }
1447:
1448: /** Tells if this Element has optional Start Tag. */
1449: public boolean hasOptionalStart() {
1450: return optStart;
1451: }
1452:
1453: /** Tells if this Element has optional End Tag. */
1454: public boolean hasOptionalEnd() {
1455: return optEnd;
1456: }
1457:
1458: /**
1459: * Get the List of Attributes of this Element, which starts with given
1460: * <CODE>prefix</CODE>.
1461: */
1462: public List getAttributeList(String prefix) {
1463: TreeSet set = new TreeSet(new Comparator() {
1464: public int compare(Object o1, Object o2) {
1465: if (isRequired(o1) && !isRequired(o2))
1466: return -1;
1467: if (!isRequired(o1) && isRequired(o2))
1468: return 1;
1469: return ((DTD.Attribute) o1).getName().compareTo(
1470: ((DTD.Attribute) o2).getName());
1471: }
1472:
1473: private final boolean isRequired(Object o) {
1474: return ((DTD.Attribute) o).getDefaultMode().equals(
1475: DTD.Attribute.MODE_REQUIRED);
1476: }
1477: });
1478: prefix = prefix.toLowerCase();
1479: Iterator i = attributes.tailMap(prefix).entrySet()
1480: .iterator();
1481:
1482: while (i.hasNext()) {
1483: Map.Entry entry = (Map.Entry) i.next();
1484: if (((String) entry.getKey()).startsWith(prefix)) {
1485: set.add(entry.getValue());
1486: } else { // we're getting data from SortedSet, so when any
1487: break; // entry fails, all remaining entry would fail.
1488: }
1489: }
1490: return new ArrayList(set);
1491: }
1492:
1493: /** Get the Attribute of given name. */
1494: public DTD.Attribute getAttribute(String name) {
1495: return (DTD.Attribute) attributes.get(name);
1496: }
1497:
1498: void addAttribute(DTD.Attribute attr) {
1499: attributes.put(attr.getName(), attr);
1500: }
1501:
1502: /** Get the content model of this Element */
1503: public DTD.ContentModel getContentModel() {
1504: return model;
1505: }
1506:
1507: public String toString() {
1508: return super .toString() + "[" + name
1509: + (optStart ? " O" : " -")
1510: + (optEnd ? " O " : " - ") + model + " attribs="
1511: + attributes + "]"; // NOI18
1512: }
1513: }
1514:
1515: /** */
1516: public static class AttributeImpl implements DTD.Attribute {
1517:
1518: private String name;
1519: private int type;
1520: private String baseType;
1521: private String typeHelper;
1522: private String defaultMode;
1523: private SortedMap values;
1524: private int hashcode;
1525:
1526: public AttributeImpl(String name, int type, String baseType,
1527: String typeHelper, String defaultMode, SortedMap values) {
1528: this .name = name;
1529: this .type = type;
1530: this .baseType = baseType;
1531: this .typeHelper = typeHelper;
1532: this .defaultMode = defaultMode;
1533: this .values = values;
1534: hashcode = name.hashCode() * (type + 1)
1535: * (baseType == null ? 1 : baseType.hashCode())
1536: + (typeHelper == null ? 1 : typeHelper.hashCode())
1537: + defaultMode.hashCode()
1538: + (values == null ? 1 : values.hashCode());
1539: }
1540:
1541: /** @return name of this attribute */
1542: public String getName() {
1543: return name;
1544: }
1545:
1546: /** @return type of this attribute */
1547: public int getType() {
1548: return type;
1549: }
1550:
1551: public String getBaseType() {
1552: return baseType;
1553: }
1554:
1555: /** The last entity name through which was this Attribute's type defined. */
1556: public String getTypeHelper() {
1557: return typeHelper;
1558: }
1559:
1560: /** This method is used to obtain default value information. */
1561: public String getDefaultMode() {
1562: return defaultMode;
1563: }
1564:
1565: /** Shorthand for determining if defaultMode is "#REQUIRED" */
1566: public boolean isRequired() {
1567: return defaultMode.equals(MODE_REQUIRED);
1568: }
1569:
1570: /**
1571: * The way how to obtain possible values for TYPE_SET Attributes
1572: *
1573: * @param prefix
1574: * required prefix, or <CODE>null</CODE>, if all possible
1575: * values are required.
1576: * @return List of Values starting with prefix, from this attribute if
1577: * it is of TYPE_SET. For other types, it doesn't make a sense
1578: * and returns null.
1579: */
1580: public List getValueList(String prefix) {
1581: if (type != TYPE_SET)
1582: return null;
1583:
1584: if (prefix == null)
1585: prefix = "";
1586: else
1587: prefix = prefix.toLowerCase();
1588:
1589: List retVal = new ArrayList();
1590: Iterator i = values.tailMap(prefix).entrySet().iterator();
1591:
1592: while (i.hasNext()) {
1593: Map.Entry entry = (Map.Entry) i.next();
1594: if (((String) entry.getKey()).startsWith(prefix)) {
1595: retVal.add(entry.getValue());
1596: } else { // we're getting data from SortedSet, so when any
1597: break; // entry fails, all remaining entry would fail.
1598: }
1599: }
1600: return retVal;
1601: }
1602:
1603: /** Get the value of given name. */
1604: public DTD.Value getValue(String name) {
1605: return (DTD.Value) values.get(name);
1606: }
1607:
1608: public String toString() {
1609: if (type == TYPE_SET) {
1610: return name + " " + values + "[" + typeHelper + "] "
1611: + defaultMode; // NOI18
1612: } else if (type == TYPE_BOOLEAN) {
1613: return name + " (" + name + ")[" + typeHelper + "] "
1614: + defaultMode; // NOI18
1615: } else {
1616: return name + " " + baseType + "[" + typeHelper + "] "
1617: + defaultMode; // NOI18
1618: }
1619: }
1620:
1621: public int hashCode() {
1622: return hashcode;
1623: }
1624:
1625: public boolean equals(Object obj) {
1626: if (!(obj instanceof AttributeImpl))
1627: return false;
1628: AttributeImpl a = (AttributeImpl) obj;
1629: return (hashcode == a.hashcode
1630: && name.equals(a.name)
1631: && type == a.type
1632: && (baseType == a.baseType || baseType != null
1633: && baseType.equals(a.baseType))
1634: && (typeHelper == a.typeHelper || typeHelper != null
1635: && typeHelper.equals(a.typeHelper))
1636: && defaultMode.equals(a.defaultMode) && (values == a.values || values != null
1637: && values.equals(a.values)));
1638: }
1639: }
1640:
1641: private static class ValueImpl implements DTD.Value {
1642: String name;
1643:
1644: ValueImpl(String name) {
1645: this .name = name;
1646: }
1647:
1648: public String getName() {
1649: return name;
1650: }
1651:
1652: public boolean equals(Object obj) {
1653: if (!(obj instanceof ValueImpl))
1654: return false;
1655: return name.equals(((ValueImpl) obj).name);
1656: }
1657:
1658: public int hashCode() {
1659: return name.hashCode();
1660: }
1661:
1662: public String toString() {
1663: return name;
1664: }
1665: }
1666:
1667: private static class CharRefImpl implements DTD.CharRef {
1668: private String name;
1669: private char value;
1670:
1671: CharRefImpl(String name, char value) {
1672: this .name = name;
1673: this .value = value;
1674: }
1675:
1676: /** @return alias to this CharRef */
1677: public String getName() {
1678: return name;
1679: }
1680:
1681: /** @return the character this alias is for */
1682: public char getValue() {
1683: return value;
1684: }
1685:
1686: public String toString() {
1687: return name + "->'" + value + "'(&#" + (int) value + ";)";
1688: }
1689:
1690: public boolean equals(Object obj) {
1691: if (!(obj instanceof CharRefImpl))
1692: return false;
1693: return name.equals(((CharRefImpl) obj).name)
1694: && value == ((CharRefImpl) obj).value;
1695: }
1696:
1697: public int hashCode() {
1698: return name.hashCode() * value;
1699: }
1700: }
1701:
1702: /** The implementation of ContentModel. It is immutable */
1703: private static class ContentModelImpl implements DTD.ContentModel {
1704: int hashcode;
1705: DTD.Content content;
1706: Set included;
1707: Set excluded;
1708:
1709: public ContentModelImpl(DTD.Content content, Set included,
1710: Set excluded) {
1711: this .content = content;
1712: this .included = included;
1713: this .excluded = excluded;
1714: hashcode = content.hashCode() + 2 * included.hashCode() + 3
1715: * excluded.hashCode();
1716: }
1717:
1718: /** @return the Content tree part of this model */
1719: public DTD.Content getContent() {
1720: return content;
1721: }
1722:
1723: /** @return Set of Element names which are recursively included. */
1724: public Set getIncludes() {
1725: return included;
1726: }
1727:
1728: /** @return Set of Element names which are recursively excluded. */
1729: public Set getExcludes() {
1730: return excluded;
1731: }
1732:
1733: public String toString() {
1734: StringBuffer sb = new StringBuffer(content.toString());
1735:
1736: if (!included.isEmpty()) {
1737: sb.append(" +(");
1738: Iterator i = included.iterator();
1739: for (;;) {
1740: sb.append(((DTD.Element) i.next()).getName());
1741: if (i.hasNext())
1742: sb.append("|");
1743: else
1744: break;
1745: }
1746: sb.append(")");
1747: }
1748:
1749: if (!excluded.isEmpty()) {
1750: sb.append(" -(");
1751: Iterator i = excluded.iterator();
1752: for (;;) {
1753: sb.append(((DTD.Element) i.next()).getName());
1754: if (i.hasNext())
1755: sb.append("|");
1756: else
1757: break;
1758: }
1759: sb.append(")");
1760: }
1761:
1762: return sb.toString();
1763: }
1764:
1765: public boolean equals(Object obj) {
1766: if (!(obj instanceof ContentModelImpl))
1767: return false;
1768: ContentModelImpl cmi = (ContentModelImpl) obj;
1769: return content.equals(cmi.content)
1770: && included.equals(cmi.included)
1771: && excluded.equals(cmi.excluded);
1772: }
1773:
1774: public int hashCode() {
1775: return hashcode;
1776: }
1777:
1778: }
1779:
1780: /**
1781: * ContentLeaf is leaf of content tree, matches just one Element name
1782: * (String)
1783: */
1784: static class ContentLeafImpl implements DTD.ContentLeaf {
1785: String elemName;
1786: DTD.Element elem;
1787:
1788: public ContentLeafImpl(String name) {
1789: this .elemName = name;
1790: }
1791:
1792: /** get the name of leaf Element */
1793: public String getName() {
1794: return elemName;
1795: }
1796:
1797: public DTD.Element getElement() {
1798: return elem;
1799: }
1800:
1801: public boolean equals(Object obj) {
1802: if (!(obj instanceof ContentLeafImpl))
1803: return false;
1804: return elemName.equals(((ContentLeafImpl) obj).elemName);
1805: }
1806:
1807: public int hashCode() {
1808: return elemName.hashCode();
1809: }
1810:
1811: public String toString() {
1812: return elemName;
1813: }
1814:
1815: /** ContentLeaf can't be discarded as it hac no operation associated */
1816: public boolean isDiscardable() {
1817: return false;
1818: }
1819:
1820: /**
1821: * ContentLeaf is either reduced to EMPTY_CONTENT or doesn't match at
1822: * all
1823: */
1824: public DTD.Content reduce(String elementName) {
1825: if (elemName.equals(elementName))
1826: return EMPTY_CONTENT;
1827: return null;
1828: }
1829:
1830: public Set getPossibleElements() {
1831: Set s = new HashSet();
1832: s.add(elem);
1833: return s;
1834: }
1835: }
1836:
1837: /** ContentNode is node of content tree */
1838: private static class UnaryContentNodeImpl implements
1839: DTD.ContentNode {
1840: int hashcode;
1841: char type;
1842: DTD.Content content;
1843:
1844: /* Constructor for unary ContentNodes */
1845: public UnaryContentNodeImpl(char type, DTD.Content content) {
1846: // sanity check:
1847: if (type != '*' && type != '?' && type != '+') {
1848: throw new IllegalArgumentException(
1849: "Unknown unary content type '" + type + "'");
1850: }
1851:
1852: this .type = type;
1853: this .content = content;
1854: hashcode = type + content.hashCode();
1855: }
1856:
1857: /** This is node, always return false */
1858: public boolean isLeaf() {
1859: return false;
1860: }
1861:
1862: /** Get the operator for this node */
1863: public char getType() {
1864: return type;
1865: }
1866:
1867: /** Get the content of this node */
1868: public DTD.Content[] getContent() {
1869: return new DTD.Content[] { content };
1870: }
1871:
1872: public boolean equals(Object obj) {
1873: if (!(obj instanceof UnaryContentNodeImpl))
1874: return false;
1875: return type == ((UnaryContentNodeImpl) obj).type
1876: && content
1877: .equals(((UnaryContentNodeImpl) obj).content);
1878: }
1879:
1880: public int hashCode() {
1881: return hashcode;
1882: }
1883:
1884: public String toString() {
1885: return content.toString() + type;
1886: }
1887:
1888: public boolean isDiscardable() {
1889: if (type == '*' || type == '?')
1890: return true;
1891: // The only remaining type, don't check: if( type == '+' )
1892: return content.isDiscardable();
1893: }
1894:
1895: public DTD.Content reduce(String elementName) {
1896: DTD.Content sub = content.reduce(elementName);
1897: if (sub == null)
1898: return null;
1899: if (sub == EMPTY_CONTENT) {
1900: if (type == '?')
1901: return EMPTY_CONTENT;
1902: if (type == '*')
1903: return this ;
1904: // '+' is the last one: if( type == '+' )
1905: // we fullfilled the '+' rule, remainder is '*'
1906: return new UnaryContentNodeImpl('*', content);
1907: }
1908: if (type == '?')
1909: return sub;
1910: DTD.Content second = (type == '*') ? this
1911: : new UnaryContentNodeImpl('*', content);
1912: return new MultiContentNodeImpl(',', new DTD.Content[] {
1913: sub, second });
1914: }
1915:
1916: public Set getPossibleElements() {
1917: return content.getPossibleElements();
1918: }
1919:
1920: }
1921:
1922: /** ContentNodeImpl is n-ary node of content tree */
1923: private static class MultiContentNodeImpl implements
1924: DTD.ContentNode {
1925: int hashcode;
1926: char type;
1927: DTD.Content[] content;
1928:
1929: /* Constructor for n-ary ContentNodes */
1930: public MultiContentNodeImpl(char type, DTD.Content[] content) {
1931: // sanity check:
1932: if (type != '|' && type != '&' && type != ',') {
1933: throw new IllegalArgumentException(
1934: "Unknown n-ary content type '" + type + "'");
1935: }
1936:
1937: this .type = type;
1938: this .content = content;
1939: hashcode = type;
1940: for (int i = 0; i < content.length; i++) {
1941: hashcode += content[i].hashCode();
1942: }
1943: }
1944:
1945: /** This is node, always return false */
1946: public boolean isLeaf() {
1947: return false;
1948: }
1949:
1950: /** Get the operator for this node */
1951: public char getType() {
1952: return type;
1953: }
1954:
1955: /** Get the content of this node */
1956: public DTD.Content[] getContent() {
1957: return content;
1958: }
1959:
1960: public boolean equals(Object obj) {
1961: if (!(obj instanceof MultiContentNodeImpl))
1962: return false;
1963: return type == ((MultiContentNodeImpl) obj).type
1964: && Arrays.equals(content,
1965: ((MultiContentNodeImpl) obj).content);
1966: }
1967:
1968: public String toString() {
1969: StringBuffer sb = new StringBuffer("(");
1970: for (int i = 0; i < content.length; i++) {
1971: sb.append(content[i].toString());
1972: if (i + 1 < content.length)
1973: sb.append(type);
1974: }
1975: sb.append(')');
1976: return sb.toString();
1977: }
1978:
1979: public int hashCode() {
1980: return hashcode;
1981: }
1982:
1983: public boolean isDiscardable() {
1984: if (type == '&' || type == ',') {
1985: for (int i = 0; i < content.length; i++) {
1986: if (!content[i].isDiscardable())
1987: return false;
1988: }
1989: return true;
1990: }
1991: // The only remaining type, don't check: if( type == '|' )
1992: for (int i = 0; i < content.length; i++) {
1993: if (content[i].isDiscardable())
1994: return true;
1995: }
1996: return false;
1997: }
1998:
1999: public DTD.Content reduce(String elementName) {
2000:
2001: if (type == '|') {
2002: for (int index = 0; index < content.length; index++) {
2003: DTD.Content sub = content[index]
2004: .reduce(elementName);
2005: if (sub != null)
2006: return sub;
2007: }
2008: return null;
2009: } else if (type == ',') {
2010: // everything before index doesn't match and is discardable
2011: int index = 0;
2012:
2013: while (index < content.length) {
2014: DTD.Content sub = content[index]
2015: .reduce(elementName);
2016: // element of sequence still don't match and isn't
2017: // discardable:
2018: if (sub == null && !content[index].isDiscardable())
2019: return null;
2020:
2021: // Element matches fully:
2022: if (sub == EMPTY_CONTENT) {
2023: int newLen = content.length - index - 1;
2024: if (newLen > 1) { // resulting sequence contains 2+
2025: // elements
2026: DTD.Content[] newSub = new DTD.Content[newLen];
2027: System.arraycopy(content, index + 1,
2028: newSub, 0, newLen);
2029: return new MultiContentNodeImpl(',', newSub);
2030: } else { // resulting sequence is one-item only
2031: return content[index + 1];
2032: }
2033: }
2034:
2035: // Element matches and is modified
2036: if (sub != null) {
2037: int newLen = content.length - index;
2038: if (newLen > 1) { // resulting sequence contains 2+
2039: // elements
2040: DTD.Content[] newSub = new DTD.Content[newLen];
2041: System.arraycopy(content, index + 1,
2042: newSub, 1, newLen - 1);
2043: newSub[0] = sub;
2044: return new MultiContentNodeImpl(',', newSub);
2045: } else { // resulting sequence is one modified item only
2046: return sub;
2047: }
2048: }
2049: index++; // discard the first element and try again
2050: }
2051:
2052: return null; // Doesn't match at all
2053: } else { // only '&' remains: if( type == '&' ) {
2054: for (int index = 0; index < content.length; index++) {
2055: DTD.Content sub = content[index]
2056: .reduce(elementName);
2057: if (sub == EMPTY_CONTENT) {
2058: int newLen = content.length - 1;
2059: if (newLen > 1) {
2060: DTD.Content[] newSub = new DTD.Content[newLen];
2061: System.arraycopy(content, 0, newSub, 0,
2062: index);
2063: if (index < newSub.length) {
2064: System.arraycopy(content, index + 1,
2065: newSub, index, newLen - index);
2066: }
2067: return new MultiContentNodeImpl('&', newSub);
2068: } else {
2069: return content[1 - index];
2070: }
2071: }
2072: if (sub != null) {
2073: DTD.Content right;
2074: if (content.length > 1) {
2075: int newLen = content.length - 1;
2076: DTD.Content[] newSub = new DTD.Content[newLen];
2077: System.arraycopy(content, 0, newSub, 0,
2078: index);
2079: if (index < newSub.length) {
2080: System.arraycopy(content, index + 1,
2081: newSub, index, newLen - index);
2082: }
2083: right = new MultiContentNodeImpl('&',
2084: newSub);
2085: } else {
2086: right = content[1 - index];
2087: }
2088: return new MultiContentNodeImpl(',',
2089: new DTD.Content[] { sub, right });
2090: }
2091: }
2092: return null;
2093:
2094: }
2095: }
2096:
2097: public Set getPossibleElements() {
2098: Set retVal = new HashSet(11);
2099:
2100: if (type == '|' || type == '&') {
2101: for (int index = 0; index < content.length; index++)
2102: retVal.addAll(content[index].getPossibleElements());
2103:
2104: } else { // only ',' remains if( type == ',' ) {}
2105: int index = 0;
2106: while (index < content.length) {
2107: retVal.addAll(content[index].getPossibleElements());
2108: if (!content[index].isDiscardable())
2109: break;
2110: index++;
2111: }
2112: }
2113: return retVal;
2114: }
2115: }
2116:
2117: }
|