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