content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.impl;
  
  import java.io.IOException;
  
  import mf.org.apache.xerces.impl.msg.XMLMessageFormatter;
  import mf.org.apache.xerces.util.SymbolTable;
  import mf.org.apache.xerces.util.XMLChar;
  import mf.org.apache.xerces.util.XMLStringBuffer;
  import mf.org.apache.xerces.util.XMLSymbols;
  import mf.org.apache.xerces.xni.Augmentations;
  import mf.org.apache.xerces.xni.XMLDTDContentModelHandler;
  import mf.org.apache.xerces.xni.XMLDTDHandler;
  import mf.org.apache.xerces.xni.XMLResourceIdentifier;
  import mf.org.apache.xerces.xni.XMLString;
  import mf.org.apache.xerces.xni.XNIException;
  import mf.org.apache.xerces.xni.parser.XMLComponent;
  import mf.org.apache.xerces.xni.parser.XMLComponentManager;
  import mf.org.apache.xerces.xni.parser.XMLConfigurationException;
  import mf.org.apache.xerces.xni.parser.XMLDTDScanner;
  import mf.org.apache.xerces.xni.parser.XMLInputSource;
  
  
  /**
   * This class is responsible for scanning the declarations found
   * in the internal and external subsets of a DTD in an XML document.
   * The scanner acts as the sources for the DTD information which is
   * communicated to the DTD handlers.
   * <p/>
   * This component requires the following features and properties from the
   * component manager that uses it:
   * <ul>
   * <li>http://xml.org/sax/features/validation</li>
   * <li>http://apache.org/xml/features/scanner/notify-char-refs</li>
   * <li>http://apache.org/xml/properties/internal/symbol-table</li>
   * <li>http://apache.org/xml/properties/internal/error-reporter</li>
   * <li>http://apache.org/xml/properties/internal/entity-manager</li>
   * </ul>
   *
   * @author Arnaud  Le Hors, IBM
   * @author Andy Clark, IBM
   * @author Glenn Marcy, IBM
   * @author Eric Ye, IBM
   * @version $Id: XMLDTDScannerImpl.java 965250 2010-07-18 16:04:58Z mrglavas $
   * @xerces.internal
   */
  public class XMLDTDScannerImpl
          extends XMLScanner
          implements XMLDTDScanner, XMLComponent, XMLEntityHandler {
  
      //
      // Constants
      //
  
      // scanner states
  
      /**
       * Scanner state: end of input.
       */
      protected static final int SCANNER_STATE_END_OF_INPUT = 0;
  
      /**
       * Scanner state: text declaration.
       */
      protected static final int SCANNER_STATE_TEXT_DECL = 1;
  
      /**
       * Scanner state: markup declaration.
       */
      protected static final int SCANNER_STATE_MARKUP_DECL = 2;
  
      // recognized features and properties
  
      /**
       * Recognized features.
       */
      private static final String[] RECOGNIZED_FEATURES = {
              VALIDATION,
              NOTIFY_CHAR_REFS,
      };
  
      /**
       * Feature defaults.
       */
      private static final Boolean[] FEATURE_DEFAULTS = {
              null,
              Boolean.FALSE,
      };
  
      /**
       * Recognized properties.
       */
      private static final String[] RECOGNIZED_PROPERTIES = {
              SYMBOL_TABLE,
              ERROR_REPORTER,
              ENTITY_MANAGER,
      };
  
      /**
       * Property defaults.
       */
      private static final Object[] PROPERTY_DEFAULTS = {
              null,
              null,
              null,
      };
  
      // debugging
  
      /**
       * Debug scanner state.
       */
      private static final boolean DEBUG_SCANNER_STATE = false;
  
      //
      // Data
      //
  
      // handlers
      /**
       * Array of 3 strings.
       */
      private final String[] fStrings = new String[3];
      /**
       * String.
       */
      private final XMLString fString = new XMLString();
  
      // state
      /**
       * String buffer.
       */
      private final XMLStringBuffer fStringBuffer = new XMLStringBuffer();
      /**
       * String buffer.
       */
      private final XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();
      /**
       * Literal text.
       */
      private final XMLString fLiteral = new XMLString();
      /**
       * Literal text.
       */
      private final XMLString fLiteral2 = new XMLString();
  
      // private data
      /**
       * Ignore conditional section buffer.
       */
      private final XMLStringBuffer fIgnoreConditionalBuffer = new XMLStringBuffer(128);
      /**
       * DTD handler.
       */
      protected XMLDTDHandler fDTDHandler;
      /**
       * DTD content model handler.
       */
      protected XMLDTDContentModelHandler fDTDContentModelHandler;
      /**
       * Scanner state.
       */
      protected int fScannerState;
      /**
       * Standalone.
       */
      protected boolean fStandalone;
      /**
       * Seen external DTD.
       */
      protected boolean fSeenExternalDTD;
      /**
       * Seen a parameter entity reference.
       */
      protected boolean fSeenPEReferences;
      /**
       * Start DTD called.
       */
      private boolean fStartDTDCalled;
      /**
       * Stack of content operators (either '|' or ',') in children
       * content.
       */
      private int[] fContentStack = new int[5];
  
      // temporary variables
      /**
       * Size of content stack.
       */
      private int fContentDepth;
      /**
       * Parameter entity stack to check well-formedness.
       */
      private int[] fPEStack = new int[5];
      /**
       * Parameter entity stack to report start/end entity calls.
       */
      private boolean[] fPEReport = new boolean[5];
      /**
       * Number of opened parameter entities.
       */
      private int fPEDepth;
      /**
       * Markup depth.
       */
      private int fMarkUpDepth;
      /**
       * Number of opened external entities.
       */
      private int fExtEntityDepth;
      /**
       * Number of opened include sections.
       */
      private int fIncludeSectDepth;
      /**
       * Enumeration values.
       */
      private String[] fEnumeration = new String[5];
      /**
       * Enumeration values count.
       */
      private int fEnumerationCount;
  
      //
      // Constructors
      //
  
      /**
       * Default constructor.
       */
      public XMLDTDScannerImpl() {
      } // <init>()
  
      /**
       * Constructor for he use of non-XMLComponentManagers.
       */
      public XMLDTDScannerImpl(SymbolTable symbolTable,
                               XMLErrorReporter errorReporter, XMLEntityManager entityManager) {
          fSymbolTable = symbolTable;
          fErrorReporter = errorReporter;
          fEntityManager = entityManager;
          entityManager.setProperty(SYMBOL_TABLE, fSymbolTable);
      }
  
      //
      // XMLDTDScanner methods
      //
  
      /**
       * Returns the scanner state name.
       */
      private static String getScannerStateName(int state) {
  
          if (DEBUG_SCANNER_STATE) {
              switch (state) {
                  case SCANNER_STATE_END_OF_INPUT:
                      return "SCANNER_STATE_END_OF_INPUT";
                  case SCANNER_STATE_TEXT_DECL:
                      return "SCANNER_STATE_TEXT_DECL";
                  case SCANNER_STATE_MARKUP_DECL:
                      return "SCANNER_STATE_MARKUP_DECL";
              }
          }
  
          return "??? (" + state + ')';
  
      } // getScannerStateName(int):String
  
      /**
       * Sets the input source.
       *
       * @param inputSource The input source or null.
       * @throws IOException Thrown on i/o error.
       */
      @Override
      public void setInputSource(XMLInputSource inputSource) throws IOException {
          if (inputSource == null) {
              // no system id was available
              if (fDTDHandler != null) {
                  fDTDHandler.startDTD(null, null);
                  fDTDHandler.endDTD(null);
              }
              return;
          }
          fEntityManager.setEntityHandler(this);
          fEntityManager.startDTDEntity(inputSource);
      } // setInputSource(XMLInputSource)
  
      /**
       * Scans the external subset of the document.
       *
       * @param complete True if the scanner should scan the document
       *                 completely, pushing all events to the registered
       *                 document handler. A value of false indicates that
       *                 that the scanner should only scan the next portion
       *                 of the document and return. A scanner instance is
       *                 permitted to completely scan a document if it does
       *                 not support this "pull" scanning model.
       * @return True if there is more to scan, false otherwise.
       */
      @Override
      public boolean scanDTDExternalSubset(boolean complete)
              throws IOException, XNIException {
  
          fEntityManager.setEntityHandler(this);
          if (fScannerState == SCANNER_STATE_TEXT_DECL) {
              fSeenExternalDTD = true;
              boolean textDecl = scanTextDecl();
              if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
                  return false;
              } else {
                  // next state is markup decls regardless of whether there
                  // is a TextDecl or not
                  setScannerState(SCANNER_STATE_MARKUP_DECL);
                  if (textDecl && !complete) {
                      return true;
                  }
              }
          }
          // keep dispatching "events"
          do {
              if (!scanDecls(complete)) {
                  return false;
              }
          } while (complete);
  
          // return that there is more to scan
          return true;
  
      } // scanDTDExternalSubset(boolean):boolean
  
      //
      // XMLComponent methods
      //
  
      /**
       * Scans the internal subset of the document.
       *
       * @param complete          True if the scanner should scan the document
       *                          completely, pushing all events to the registered
       *                          document handler. A value of false indicates that
       *                          that the scanner should only scan the next portion
       *                          of the document and return. A scanner instance is
       *                          permitted to completely scan a document if it does
       *                          not support this "pull" scanning model.
       * @param standalone        True if the document was specified as standalone.
       *                          This value is important for verifying certain
       *                          well-formedness constraints.
       * @param hasExternalSubset True if the document has an external DTD.
       *                          This allows the scanner to properly notify
       *                          the handler of the end of the DTD in the
       *                          absence of an external subset.
       * @return True if there is more to scan, false otherwise.
       */
      @Override
      public boolean scanDTDInternalSubset(boolean complete, boolean standalone,
                                           boolean hasExternalSubset)
              throws IOException, XNIException {
          // reset entity scanner
          fEntityScanner = fEntityManager.getEntityScanner();
          fEntityManager.setEntityHandler(this);
          fStandalone = standalone;
          if (fScannerState == SCANNER_STATE_TEXT_DECL) {
              // call handler
              if (fDTDHandler != null) {
                  fDTDHandler.startDTD(fEntityScanner, null);
                  fStartDTDCalled = true;
              }
              // set starting state for internal subset
              setScannerState(SCANNER_STATE_MARKUP_DECL);
          }
          // keep dispatching "events"
          do {
              if (!scanDecls(complete)) {
                  // call handler
                  if (fDTDHandler != null && hasExternalSubset == false) {
                      fDTDHandler.endDTD(null);
                  }
                  // we're done, set starting state for external subset
                  setScannerState(SCANNER_STATE_TEXT_DECL);
                  return false;
              }
          } while (complete);
  
          // return that there is more to scan
          return true;
  
      } // scanDTDInternalSubset(boolean,boolean,boolean):boolean
  
      /**
       * reset
       *
       * @param componentManager
       */
      @Override
      public void reset(XMLComponentManager componentManager)
              throws XMLConfigurationException {
  
          super.reset(componentManager);
          init();
  
      } // reset(XMLComponentManager)
  
      // this is made for something like XMLDTDLoader--XMLComponentManager-free operation...
      @Override
      public void reset() {
          super.reset();
          init();
      }
  
      /**
       * Returns a list of feature identifiers that are recognized by
       * this component. This method may return null if no features
       * are recognized by this component.
       */
      @Override
      public String[] getRecognizedFeatures() {
          return (RECOGNIZED_FEATURES.clone());
      } // getRecognizedFeatures():String[]
  
      /**
       * Returns a list of property identifiers that are recognized by
       * this component. This method may return null if no properties
       * are recognized by this component.
       */
      @Override
      public String[] getRecognizedProperties() {
          return (RECOGNIZED_PROPERTIES.clone());
      } // getRecognizedProperties():String[]
  
      /**
       * Returns the default state for a feature, or null if this
       * component does not want to report a default value for this
       * feature.
       *
       * @param featureId The feature identifier.
       * @since Xerces 2.2.0
       */
      @Override
      public Boolean getFeatureDefault(String featureId) {
          for (int i = 0; i < RECOGNIZED_FEATURES.length; i++) {
              if (RECOGNIZED_FEATURES[i].equals(featureId)) {
                  return FEATURE_DEFAULTS[i];
              }
          }
          return null;
      } // getFeatureDefault(String):Boolean
  
      //
      // XMLDTDSource methods
      //
  
      /**
       * Returns the default state for a property, or null if this
       * component does not want to report a default value for this
       * property.
       *
       * @param propertyId The property identifier.
       * @since Xerces 2.2.0
       */
      @Override
      public Object getPropertyDefault(String propertyId) {
          for (int i = 0; i < RECOGNIZED_PROPERTIES.length; i++) {
              if (RECOGNIZED_PROPERTIES[i].equals(propertyId)) {
                  return PROPERTY_DEFAULTS[i];
              }
          }
          return null;
      } // getPropertyDefault(String):Object
  
      /**
       * getDTDHandler
       *
       * @return the XMLDTDHandler
       */
      @Override
      public XMLDTDHandler getDTDHandler() {
          return fDTDHandler;
      } // getDTDHandler():  XMLDTDHandler
  
      //
      // XMLDTDContentModelSource methods
      //
  
      /**
       * setDTDHandler
       *
       * @param dtdHandler
       */
      @Override
      public void setDTDHandler(XMLDTDHandler dtdHandler) {
          fDTDHandler = dtdHandler;
      } // setDTDHandler(XMLDTDHandler)
  
      /**
       * getDTDContentModelHandler
       *
       * @return XMLDTDContentModelHandler
       */
      @Override
      public XMLDTDContentModelHandler getDTDContentModelHandler() {
          return fDTDContentModelHandler;
      } // setDTDContentModelHandler
  
      //
      // XMLEntityHandler methods
      //
  
      /**
       * setDTDContentModelHandler
       *
       * @param dtdContentModelHandler
       */
      @Override
      public void setDTDContentModelHandler(XMLDTDContentModelHandler
                                                    dtdContentModelHandler) {
          fDTDContentModelHandler = dtdContentModelHandler;
      } // setDTDContentModelHandler
  
      /**
       * This method notifies of the start of an entity. The DTD has the
       * pseudo-name of "[dtd]" parameter entity names start with '%'; and
       * general entities are just specified by their name.
       *
       * @param name       The name of the entity.
       * @param identifier The resource identifier.
       * @param encoding   The auto-detected IANA encoding name of the entity
       *                   stream. This value will be null in those situations
       *                   where the entity encoding is not auto-detected (e.g.
       *                   internal entities or a document entity that is
       *                   parsed from a java.io.Reader).
       * @param augs       Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void startEntity(String name,
                              XMLResourceIdentifier identifier,
                              String encoding, Augmentations augs) throws XNIException {
  
          super.startEntity(name, identifier, encoding, augs);
  
          boolean dtdEntity = name.equals("[dtd]");
          if (dtdEntity) {
              // call handler
              if (fDTDHandler != null && !fStartDTDCalled) {
                  fDTDHandler.startDTD(fEntityScanner, null);
              }
              if (fDTDHandler != null) {
                  fDTDHandler.startExternalSubset(identifier, null);
              }
              fEntityManager.startExternalSubset();
              fExtEntityDepth++;
          } else if (name.charAt(0) == '%') {
              pushPEStack(fMarkUpDepth, fReportEntity);
              if (fEntityScanner.isExternal()) {
                  fExtEntityDepth++;
              }
          }
  
          // call handler
          if (fDTDHandler != null && !dtdEntity && fReportEntity) {
              fDTDHandler.startParameterEntity(name, identifier, encoding, augs);
          }
  
      } // startEntity(String,XMLResourceIdentifier,String)
  
      // helper methods
  
      /**
       * This method notifies the end of an entity. The DTD has the pseudo-name
       * of "[dtd]" parameter entity names start with '%'; and general entities
       * are just specified by their name.
       *
       * @param name The name of the entity.
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void endEntity(String name, Augmentations augs)
              throws XNIException {
  
          super.endEntity(name, augs);
  
          // if there is no data after the doctype
          //
          if (fScannerState == SCANNER_STATE_END_OF_INPUT)
              return;
  
          // Handle end of PE
          boolean reportEntity = fReportEntity;
          if (name.startsWith("%")) {
              reportEntity = peekReportEntity();
              // check well-formedness of the enity
              int startMarkUpDepth = popPEStack();
              // throw fatalError if this entity was incomplete and
              // was a freestanding decl
              if (startMarkUpDepth == 0 &&
                      startMarkUpDepth < fMarkUpDepth) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "ILL_FORMED_PARAMETER_ENTITY_WHEN_USED_IN_DECL",
                          new Object[]{fEntityManager.fCurrentEntity.name},
                          XMLErrorReporter.SEVERITY_FATAL_ERROR);
              }
              if (startMarkUpDepth != fMarkUpDepth) {
                  reportEntity = false;
                  if (fValidation) {
                      // Proper nesting of parameter entities is a Validity Constraint
                      // and must not be enforced when validation is off
                      fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                              "ImproperDeclarationNesting",
                              new Object[]{name},
                              XMLErrorReporter.SEVERITY_ERROR);
                  }
              }
              if (fEntityScanner.isExternal()) {
                  fExtEntityDepth--;
              }
              // call handler
              if (fDTDHandler != null && reportEntity) {
                  fDTDHandler.endParameterEntity(name, augs);
              }
          }
          // end DTD
          else if (name.equals("[dtd]")) {
              if (fIncludeSectDepth != 0) {
                  reportFatalError("IncludeSectUnterminated", null);
              }
              fScannerState = SCANNER_STATE_END_OF_INPUT;
              // call handler
              fEntityManager.endExternalSubset();
              if (fDTDHandler != null) {
                  fDTDHandler.endExternalSubset(null);
                  fDTDHandler.endDTD(null);
              }
              fExtEntityDepth--;
          }
  
      } // endEntity(String)
  
      //
      // Private methods
      //
  
      /**
       * Sets the scanner state.
       *
       * @param state The new scanner state.
       */
      protected final void setScannerState(int state) {
  
          fScannerState = state;
          if (DEBUG_SCANNER_STATE) {
              System.out.print("### setScannerState: ");
              System.out.print(getScannerStateName(state));
              System.out.println();
          }
  
      } // setScannerState(int)
  
      protected final boolean scanningInternalSubset() {
          return fExtEntityDepth == 0;
      }
  
      /**
       * start a parameter entity dealing with the textdecl if there is any
       *
       * @param name    The name of the parameter entity to start (without the '%')
       * @param literal Whether this is happening within a literal
       */
      protected void startPE(String name, boolean literal)
              throws IOException, XNIException {
          int depth = fPEDepth;
          String pName = "%" + name;
          if (!fSeenPEReferences) {
              fSeenPEReferences = true;
              fEntityManager.notifyHasPEReferences();
          }
          if (fValidation && !fEntityManager.isDeclaredEntity(pName)) {
              fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN, "EntityNotDeclared",
                      new Object[]{name}, XMLErrorReporter.SEVERITY_ERROR);
          }
          fEntityManager.startEntity(fSymbolTable.addSymbol(pName),
                  literal);
          // if we actually got a new entity and it's external
          // parse text decl if there is any
          if (depth != fPEDepth && fEntityScanner.isExternal()) {
              scanTextDecl();
          }
      }
  
      /**
       * Dispatch an XML "event".
       *
       * @return true if a TextDecl was scanned.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown on parse error.
       */
      protected final boolean scanTextDecl()
              throws IOException, XNIException {
  
          // scan XMLDecl
          boolean textDecl = false;
          if (fEntityScanner.skipString("<?xml")) {
              fMarkUpDepth++;
              // NOTE: special case where document starts with a PI
              //       whose name starts with "xml" (e.g. "xmlfoo")
              if (isValidNameChar(fEntityScanner.peekChar())) {
                  fStringBuffer.clear();
                  fStringBuffer.append("xml");
                  if (fNamespaces) {
                      while (isValidNCName(fEntityScanner.peekChar())) {
                          fStringBuffer.append((char) fEntityScanner.scanChar());
                      }
                  } else {
                      while (isValidNameChar(fEntityScanner.peekChar())) {
                          fStringBuffer.append((char) fEntityScanner.scanChar());
                      }
                  }
                  String target =
                          fSymbolTable.addSymbol(fStringBuffer.ch,
                                  fStringBuffer.offset,
                                  fStringBuffer.length);
                  scanPIData(target, fString);
              }
  
              // standard Text declaration
              else {
                  // pseudo-attribute values
                  String version = null;
                  String encoding = null;
  
                  scanXMLDeclOrTextDecl(true, fStrings);
                  textDecl = true;
                  fMarkUpDepth--;
  
                  version = fStrings[0];
                  encoding = fStrings[1];
  
                  fEntityScanner.setXMLVersion(version);
                  if (!fEntityScanner.fCurrentEntity.isEncodingExternallySpecified()) {
                      fEntityScanner.setEncoding(encoding);
                  }
  
                  // call handler
                  if (fDTDHandler != null) {
                      fDTDHandler.textDecl(version, encoding, null);
                  }
              }
          }
          fEntityManager.fCurrentEntity.mayReadChunks = true;
  
          return textDecl;
  
      } // scanTextDecl(boolean):boolean
  
      /**
       * Scans a processing data. This is needed to handle the situation
       * where a document starts with a processing instruction whose
       * target name <em>starts with</em> "xml". (e.g. xmlfoo)
       *
       * @param target The PI target
       * @param data   The string to fill in with the data
       */
      @Override
      protected final void scanPIData(String target, XMLString data)
              throws IOException, XNIException {
  
          super.scanPIData(target, data);
          fMarkUpDepth--;
  
          // call handler
          if (fDTDHandler != null) {
              fDTDHandler.processingInstruction(target, data, null);
          }
  
      } // scanPIData(String)
  
      /**
       * Scans a comment.
       * <p/>
       * <pre>
       * [15] Comment ::= '&lt!--' ((Char - '-') | ('-' (Char - '-')))* '-->'
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!--'
       */
      protected final void scanComment() throws IOException, XNIException {
  
          fReportEntity = false;
          scanComment(fStringBuffer);
          fMarkUpDepth--;
  
          // call handler
          if (fDTDHandler != null) {
              fDTDHandler.comment(fStringBuffer, null);
          }
          fReportEntity = true;
  
      } // scanComment()
  
      /**
       * Scans an element declaration
       * <p/>
       * <pre>
       * [45]    elementdecl    ::=    '&lt;!ELEMENT' S Name S contentspec S? '>'
       * [46]    contentspec    ::=    'EMPTY' | 'ANY' | Mixed | children
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!ELEMENT'
       */
      protected final void scanElementDecl() throws IOException, XNIException {
  
          // spaces
          fReportEntity = false;
          if (!skipSeparator(true, !scanningInternalSubset())) {
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ELEMENTDECL",
                      null);
          }
  
          // element name
          String name = fEntityScanner.scanName();
          if (name == null) {
              reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ELEMENTDECL",
                      null);
          }
  
          // spaces
          if (!skipSeparator(true, !scanningInternalSubset())) {
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_CONTENTSPEC_IN_ELEMENTDECL",
                      new Object[]{name});
          }
  
          // content model
          if (fDTDContentModelHandler != null) {
              fDTDContentModelHandler.startContentModel(name, null);
          }
          String contentModel = null;
          fReportEntity = true;
          if (fEntityScanner.skipString("EMPTY")) {
              contentModel = "EMPTY";
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.empty(null);
              }
          } else if (fEntityScanner.skipString("ANY")) {
              contentModel = "ANY";
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.any(null);
              }
          } else {
              if (!fEntityScanner.skipChar('(')) {
                  reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
                          new Object[]{name});
              }
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.startGroup(null);
              }
              fStringBuffer.clear();
              fStringBuffer.append('(');
              fMarkUpDepth++;
              skipSeparator(false, !scanningInternalSubset());
  
              // Mixed content model
              if (fEntityScanner.skipString("#PCDATA")) {
                  scanMixed(name);
              } else {              // children content
                  scanChildren(name);
              }
              contentModel = fStringBuffer.toString();
          }
  
          // call handler
          if (fDTDContentModelHandler != null) {
              fDTDContentModelHandler.endContentModel(null);
          }
  
          fReportEntity = false;
          skipSeparator(false, !scanningInternalSubset());
          // end
          if (!fEntityScanner.skipChar('>')) {
              reportFatalError("ElementDeclUnterminated", new Object[]{name});
          }
          fReportEntity = true;
          fMarkUpDepth--;
  
          // call handler
          if (fDTDHandler != null) {
              fDTDHandler.elementDecl(name, contentModel, null);
          }
  
      } // scanElementDecl()
  
      /**
       * scan Mixed content model
       * This assumes the content model has been parsed up to #PCDATA and
       * can simply append to fStringBuffer.
       * <pre>
       * [51]    Mixed    ::=    '(' S? '#PCDATA' (S? '|' S? Name)* S? ')*'
       *                       | '(' S? '#PCDATA' S? ')'
       * </pre>
       *
       * @param elName The element type name this declaration is about.
       *               <p/>
       *               <strong>Note:</strong> Called after scanning past '(#PCDATA'.
       */
      private final void scanMixed(String elName)
              throws IOException, XNIException {
  
          String childName = null;
  
          fStringBuffer.append("#PCDATA");
          // call handler
          if (fDTDContentModelHandler != null) {
              fDTDContentModelHandler.pcdata(null);
          }
          skipSeparator(false, !scanningInternalSubset());
          while (fEntityScanner.skipChar('|')) {
              fStringBuffer.append('|');
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
                          null);
              }
              skipSeparator(false, !scanningInternalSubset());
  
              childName = fEntityScanner.scanName();
              if (childName == null) {
                  reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_MIXED_CONTENT",
                          new Object[]{elName});
              }
              fStringBuffer.append(childName);
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.element(childName, null);
              }
              skipSeparator(false, !scanningInternalSubset());
          }
          // The following check must be done in a single call (as opposed to one
          // for ')' and then one for '*') to guarantee that callbacks are
          // properly nested. We do not want to trigger endEntity too early in
          // case we cross the boundary of an entity between the two characters.
          if (fEntityScanner.skipString(")*")) {
              fStringBuffer.append(")*");
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.endGroup(null);
                  fDTDContentModelHandler.occurrence(XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE,
                          null);
              }
          } else if (childName != null) {
              reportFatalError("MixedContentUnterminated",
                      new Object[]{elName});
          } else if (fEntityScanner.skipChar(')')) {
              fStringBuffer.append(')');
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.endGroup(null);
              }
          } else {
              reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
                      new Object[]{elName});
          }
          fMarkUpDepth--;
          // we are done
      }
  
      /**
       * scan children content model
       * This assumes it can simply append to fStringBuffer.
       * <pre>
       * [47]    children  ::=    (choice | seq) ('?' | '*' | '+')?
       * [48]    cp        ::=    (Name | choice | seq) ('?' | '*' | '+')?
       * [49]    choice    ::=    '(' S? cp ( S? '|' S? cp )+ S? ')'
       * [50]    seq       ::=    '(' S? cp ( S? ',' S? cp )* S? ')'
       * </pre>
       *
       * @param elName The element type name this declaration is about.
       *               <p/>
       *               <strong>Note:</strong> Called after scanning past the first open
       *               paranthesis.
       */
      private final void scanChildren(String elName)
              throws IOException, XNIException {
  
          fContentDepth = 0;
          pushContentStack(0);
          int currentOp = 0;
          int c;
          while (true) {
              if (fEntityScanner.skipChar('(')) {
                  fMarkUpDepth++;
                  fStringBuffer.append('(');
                  // call handler
                  if (fDTDContentModelHandler != null) {
                      fDTDContentModelHandler.startGroup(null);
                  }
                  // push current op on stack and reset it
                  pushContentStack(currentOp);
                  currentOp = 0;
                  skipSeparator(false, !scanningInternalSubset());
                  continue;
              }
              skipSeparator(false, !scanningInternalSubset());
              String childName = fEntityScanner.scanName();
              if (childName == null) {
                  reportFatalError("MSG_OPEN_PAREN_OR_ELEMENT_TYPE_REQUIRED_IN_CHILDREN",
                          new Object[]{elName});
                  return;
              }
              // call handler
              if (fDTDContentModelHandler != null) {
                  fDTDContentModelHandler.element(childName, null);
              }
              fStringBuffer.append(childName);
              c = fEntityScanner.peekChar();
              if (c == '?' || c == '*' || c == '+') {
                  // call handler
                  if (fDTDContentModelHandler != null) {
                      short oc;
                      if (c == '?') {
                          oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
                      } else if (c == '*') {
                          oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
                      } else {
                          oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
                      }
                      fDTDContentModelHandler.occurrence(oc, null);
                  }
                  fEntityScanner.scanChar();
                  fStringBuffer.append((char) c);
              }
              while (true) {
                  skipSeparator(false, !scanningInternalSubset());
                  c = fEntityScanner.peekChar();
                  if (c == ',' && currentOp != '|') {
                      currentOp = c;
                      // call handler
                      if (fDTDContentModelHandler != null) {
                          fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_SEQUENCE,
                                  null);
                      }
                      fEntityScanner.scanChar();
                      fStringBuffer.append(',');
                      break;
                  } else if (c == '|' && currentOp != ',') {
                      currentOp = c;
                      // call handler
                      if (fDTDContentModelHandler != null) {
                          fDTDContentModelHandler.separator(XMLDTDContentModelHandler.SEPARATOR_CHOICE,
                                  null);
                      }
                      fEntityScanner.scanChar();
                      fStringBuffer.append('|');
                      break;
                  } else if (c != ')') {
                      reportFatalError("MSG_CLOSE_PAREN_REQUIRED_IN_CHILDREN",
                              new Object[]{elName});
                  }
                  // call handler
                  if (fDTDContentModelHandler != null) {
                      fDTDContentModelHandler.endGroup(null);
                  }
                  // restore previous op
                  currentOp = popContentStack();
                  short oc;
                  // The following checks must be done in a single call (as
                  // opposed to one for ')' and then one for '?', '*', and '+')
                  // to guarantee that callbacks are properly nested. We do not
                  // want to trigger endEntity too early in case we cross the
                  // boundary of an entity between the two characters.
                  if (fEntityScanner.skipString(")?")) {
                      fStringBuffer.append(")?");
                      // call handler
                      if (fDTDContentModelHandler != null) {
                          oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_ONE;
                          fDTDContentModelHandler.occurrence(oc, null);
                      }
                  } else if (fEntityScanner.skipString(")+")) {
                      fStringBuffer.append(")+");
                      // call handler
                      if (fDTDContentModelHandler != null) {
                          oc = XMLDTDContentModelHandler.OCCURS_ONE_OR_MORE;
                          fDTDContentModelHandler.occurrence(oc, null);
                      }
                  } else if (fEntityScanner.skipString(")*")) {
                      fStringBuffer.append(")*");
                      // call handler
                      if (fDTDContentModelHandler != null) {
                          oc = XMLDTDContentModelHandler.OCCURS_ZERO_OR_MORE;
                          fDTDContentModelHandler.occurrence(oc, null);
                      }
                  } else {
                      // no occurrence specified
                      fEntityScanner.scanChar();
                      fStringBuffer.append(')');
                  }
                  fMarkUpDepth--;
                  if (fContentDepth == 0) {
                      return;
                  }
              }
              skipSeparator(false, !scanningInternalSubset());
          }
      }
  
      /**
       * Scans an attlist declaration
       * <p/>
       * <pre>
       * [52]  AttlistDecl    ::=   '&lt;!ATTLIST' S Name AttDef* S? '>'
       * [53]  AttDef         ::=   S Name S AttType S DefaultDecl
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!ATTLIST'
       */
      protected final void scanAttlistDecl() throws IOException, XNIException {
  
          // spaces
          fReportEntity = false;
          if (!skipSeparator(true, !scanningInternalSubset())) {
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ELEMENT_TYPE_IN_ATTLISTDECL",
                      null);
          }
  
          // element name
          String elName = fEntityScanner.scanName();
          if (elName == null) {
              reportFatalError("MSG_ELEMENT_TYPE_REQUIRED_IN_ATTLISTDECL",
                      null);
          }
  
          // call handler
          if (fDTDHandler != null) {
              fDTDHandler.startAttlist(elName, null);
          }
  
          // spaces
          if (!skipSeparator(true, !scanningInternalSubset())) {
              // no space, is it the end yet?
              if (fEntityScanner.skipChar('>')) {
                  // yes, stop here
                  // call handler
                  if (fDTDHandler != null) {
                      fDTDHandler.endAttlist(null);
                  }
                  fMarkUpDepth--;
                  return;
              } else {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTRIBUTE_NAME_IN_ATTDEF",
                          new Object[]{elName});
              }
          }
  
          // definitions
          while (!fEntityScanner.skipChar('>')) {
              String name = fEntityScanner.scanName();
              if (name == null) {
                  reportFatalError("AttNameRequiredInAttDef",
                          new Object[]{elName});
              }
              // spaces
              if (!skipSeparator(true, !scanningInternalSubset())) {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ATTTYPE_IN_ATTDEF",
                          new Object[]{elName, name});
              }
              // type
              String type = scanAttType(elName, name);
  
              // spaces
              if (!skipSeparator(true, !scanningInternalSubset())) {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_DEFAULTDECL_IN_ATTDEF",
                          new Object[]{elName, name});
              }
  
              // default decl
              String defaultType = scanAttDefaultDecl(elName, name,
                      type,
                      fLiteral, fLiteral2);
              // REVISIT: Should we do anything with the non-normalized
              //          default attribute value? -Ac
              // yes--according to bug 5073.  - neilg
  
              // call handler
              if (fDTDHandler != null) {
                  String[] enumeration = null;
                  if (fEnumerationCount != 0) {
                      enumeration = new String[fEnumerationCount];
                      System.arraycopy(fEnumeration, 0, enumeration,
                              0, fEnumerationCount);
                  }
                  // Determine whether the default value to be passed should be null. 
                  // REVISIT: should probably check whether fLiteral.ch is null instead. LM.
                  if (defaultType != null && (defaultType.equals("#REQUIRED") ||
                          defaultType.equals("#IMPLIED"))) {
                      fDTDHandler.attributeDecl(elName, name, type, enumeration,
                              defaultType, null, null, null);
                  } else {
                      fDTDHandler.attributeDecl(elName, name, type, enumeration,
                              defaultType, fLiteral, fLiteral2, null);
                  }
              }
              skipSeparator(false, !scanningInternalSubset());
          }
  
          // call handler
          if (fDTDHandler != null) {
              fDTDHandler.endAttlist(null);
          }
          fMarkUpDepth--;
          fReportEntity = true;
  
      } // scanAttlistDecl()
  
      /**
       * Scans an attribute type definition
       * <p/>
       * <pre>
       * [54]  AttType        ::=   StringType | TokenizedType | EnumeratedType
       * [55]  StringType     ::=   'CDATA'
       * [56]  TokenizedType  ::=   'ID'
       *                          | 'IDREF'
       *                          | 'IDREFS'
       *                          | 'ENTITY'
       *                          | 'ENTITIES'
       *                          | 'NMTOKEN'
       *                          | 'NMTOKENS'
       * [57]  EnumeratedType ::=    NotationType | Enumeration
       * [58]  NotationType ::= 'NOTATION' S '(' S? Name (S? '|' S? Name)* S? ')'
       * [59]  Enumeration    ::=    '(' S? Nmtoken (S? '|' S? Nmtoken)* S? ')'
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!ATTLIST'
       *
       * @param elName The element type name this declaration is about.
       * @param atName The attribute name this declaration is about.
       */
      private final String scanAttType(String elName, String atName)
              throws IOException, XNIException {
  
          String type = null;
          fEnumerationCount = 0;
          /*
           * Watchout: the order here is important: when a string happens to
           * be a substring of another string, the longer one needs to be
           * looked for first!!
           */
          if (fEntityScanner.skipString("CDATA")) {
              type = "CDATA";
          } else if (fEntityScanner.skipString("IDREFS")) {
              type = "IDREFS";
          } else if (fEntityScanner.skipString("IDREF")) {
              type = "IDREF";
          } else if (fEntityScanner.skipString("ID")) {
              type = "ID";
          } else if (fEntityScanner.skipString("ENTITY")) {
              type = "ENTITY";
          } else if (fEntityScanner.skipString("ENTITIES")) {
              type = "ENTITIES";
          } else if (fEntityScanner.skipString("NMTOKENS")) {
              type = "NMTOKENS";
          } else if (fEntityScanner.skipString("NMTOKEN")) {
              type = "NMTOKEN";
          } else if (fEntityScanner.skipString("NOTATION")) {
              type = "NOTATION";
              // spaces
              if (!skipSeparator(true, !scanningInternalSubset())) {
                  reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_IN_NOTATIONTYPE",
                          new Object[]{elName, atName});
              }
              // open paren
              int c = fEntityScanner.scanChar();
              if (c != '(') {
                  reportFatalError("MSG_OPEN_PAREN_REQUIRED_IN_NOTATIONTYPE",
                          new Object[]{elName, atName});
              }
              fMarkUpDepth++;
              do {
                  skipSeparator(false, !scanningInternalSubset());
                  String aName = fEntityScanner.scanName();
                  if (aName == null) {
                      reportFatalError("MSG_NAME_REQUIRED_IN_NOTATIONTYPE",
                              new Object[]{elName, atName});
                      c = skipInvalidEnumerationValue();
                      if (c == '|') {
                          continue;
                      }
                      break;
                  }
                  ensureEnumerationSize(fEnumerationCount + 1);
                  fEnumeration[fEnumerationCount++] = aName;
                  skipSeparator(false, !scanningInternalSubset());
                  c = fEntityScanner.scanChar();
              } while (c == '|');
              if (c != ')') {
                  reportFatalError("NotationTypeUnterminated",
                          new Object[]{elName, atName});
              }
              fMarkUpDepth--;
          } else {              // Enumeration
              type = "ENUMERATION";
              // open paren
              int c = fEntityScanner.scanChar();
              if (c != '(') {
  //                       "OPEN_PAREN_REQUIRED_BEFORE_ENUMERATION_IN_ATTRDECL",
                  reportFatalError("AttTypeRequiredInAttDef",
                          new Object[]{elName, atName});
              }
              fMarkUpDepth++;
              do {
                  skipSeparator(false, !scanningInternalSubset());
                  String token = fEntityScanner.scanNmtoken();
                  if (token == null) {
                      reportFatalError("MSG_NMTOKEN_REQUIRED_IN_ENUMERATION",
                              new Object[]{elName, atName});
                      c = skipInvalidEnumerationValue();
                      if (c == '|') {
                          continue;
                      }
                      break;
                  }
                  ensureEnumerationSize(fEnumerationCount + 1);
                  fEnumeration[fEnumerationCount++] = token;
                  skipSeparator(false, !scanningInternalSubset());
                  c = fEntityScanner.scanChar();
              } while (c == '|');
              if (c != ')') {
                  reportFatalError("EnumerationUnterminated",
                          new Object[]{elName, atName});
              }
              fMarkUpDepth--;
          }
          return type;
  
      } // scanAttType():String
  
  
      /**
       * Scans an attribute default declaration
       * <p/>
       * <pre>
       * [60] DefaultDecl ::= '#REQUIRED' | '#IMPLIED' | (('#FIXED' S)? AttValue)
       * </pre>
       *
       * @param elName
       * @param atName                  The name of the attribute being scanned.
       * @param type
       * @param defaultVal              The string to fill in with the default value.
       * @param nonNormalizedDefaultVal
       */
      protected final String scanAttDefaultDecl(String elName, String atName,
                                                String type,
                                                XMLString defaultVal,
                                                XMLString nonNormalizedDefaultVal)
              throws IOException, XNIException {
  
          String defaultType = null;
          fString.clear();
          defaultVal.clear();
          if (fEntityScanner.skipString("#REQUIRED")) {
              defaultType = "#REQUIRED";
          } else if (fEntityScanner.skipString("#IMPLIED")) {
              defaultType = "#IMPLIED";
          } else {
              if (fEntityScanner.skipString("#FIXED")) {
                  defaultType = "#FIXED";
                  // spaces
                  if (!skipSeparator(true, !scanningInternalSubset())) {
                      reportFatalError("MSG_SPACE_REQUIRED_AFTER_FIXED_IN_DEFAULTDECL",
                              new Object[]{elName, atName});
                  }
              }
              // AttValue 
              boolean isVC = !fStandalone && (fSeenExternalDTD || fSeenPEReferences);
              scanAttributeValue(defaultVal, nonNormalizedDefaultVal, atName, isVC, elName);
          }
          return defaultType;
  
      } // ScanAttDefaultDecl
  
      /**
       * Scans an entity declaration
       * <p/>
       * <pre>
       * [70]    EntityDecl  ::=    GEDecl | PEDecl
       * [71]    GEDecl      ::=    '&lt;!ENTITY' S Name S EntityDef S? '>'
       * [72]    PEDecl      ::=    '&lt;!ENTITY' S '%' S Name S PEDef S? '>'
       * [73]    EntityDef   ::=    EntityValue | (ExternalID NDataDecl?)
       * [74]    PEDef       ::=    EntityValue | ExternalID
       * [75]    ExternalID  ::=    'SYSTEM' S SystemLiteral
       *                          | 'PUBLIC' S PubidLiteral S SystemLiteral
       * [76]    NDataDecl   ::=    S 'NDATA' S Name
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!ENTITY'
       */
      private final void scanEntityDecl() throws IOException, XNIException {
  
          boolean isPEDecl = false;
          boolean sawPERef = false;
          fReportEntity = false;
          if (fEntityScanner.skipSpaces()) {
              if (!fEntityScanner.skipChar('%')) {
                  isPEDecl = false; // <!ENTITY x "x">
              } else if (skipSeparator(true, !scanningInternalSubset())) {
                  // <!ENTITY % x "x">
                  isPEDecl = true;
              } else if (scanningInternalSubset()) {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_PEDECL",
                          null);
                  isPEDecl = true;
              } else if (fEntityScanner.peekChar() == '%') {
                  // <!ENTITY %%x; "x"> is legal
                  skipSeparator(false, !scanningInternalSubset());
                  isPEDecl = true;
              } else {
                  sawPERef = true;
              }
          } else if (scanningInternalSubset() || !fEntityScanner.skipChar('%')) {
              // <!ENTITY[^ ]...> or <!ENTITY[^ %]...>
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_ENTITY_NAME_IN_ENTITYDECL",
                      null);
              isPEDecl = false;
          } else if (fEntityScanner.skipSpaces()) {
              // <!ENTITY% ...>
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_PERCENT_IN_PEDECL",
                      null);
              isPEDecl = false;
          } else {
              sawPERef = true;
          }
          if (sawPERef) {
              while (true) {
                  String peName = fEntityScanner.scanName();
                  if (peName == null) {
                      reportFatalError("NameRequiredInPEReference", null);
                  } else if (!fEntityScanner.skipChar(';')) {
                      reportFatalError("SemicolonRequiredInPEReference",
                              new Object[]{peName});
                  } else {
                      startPE(peName, false);
                  }
                  fEntityScanner.skipSpaces();
                  if (!fEntityScanner.skipChar('%'))
                      break;
                  if (!isPEDecl) {
                      if (skipSeparator(true, !scanningInternalSubset())) {
                          isPEDecl = true;
                          break;
                      }
                      isPEDecl = fEntityScanner.skipChar('%');
                  }
              }
          }
  
          // name
          String name = null;
          if (fNamespaces) {
              name = fEntityScanner.scanNCName();
          } else {
              name = fEntityScanner.scanName();
          }
          if (name == null) {
              reportFatalError("MSG_ENTITY_NAME_REQUIRED_IN_ENTITYDECL", null);
          }
          // spaces
          if (!skipSeparator(true, !scanningInternalSubset())) {
              if (fNamespaces && fEntityScanner.peekChar() == ':') {
                  fEntityScanner.scanChar();
                  XMLStringBuffer colonName = new XMLStringBuffer(name);
                  colonName.append(':');
                  String str = fEntityScanner.scanName();
                  if (str != null)
                      colonName.append(str);
                  reportFatalError("ColonNotLegalWithNS", new Object[]{colonName.toString()});
                  if (!skipSeparator(true, !scanningInternalSubset())) {
                      reportFatalError("MSG_SPACE_REQUIRED_AFTER_ENTITY_NAME_IN_ENTITYDECL",
                              new Object[]{name});
                  }
              } else {
                  reportFatalError("MSG_SPACE_REQUIRED_AFTER_ENTITY_NAME_IN_ENTITYDECL",
                          new Object[]{name});
              }
          }
  
          // external id
          scanExternalID(fStrings, false);
          String systemId = fStrings[0];
          String publicId = fStrings[1];
  
          String notation = null;
          // NDATA
          boolean sawSpace = skipSeparator(true, !scanningInternalSubset());
          if (!isPEDecl && fEntityScanner.skipString("NDATA")) {
              // check whether there was space before NDATA
              if (!sawSpace) {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NDATA_IN_UNPARSED_ENTITYDECL",
                          new Object[]{name});
              }
  
              // spaces
              if (!skipSeparator(true, !scanningInternalSubset())) {
                  reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_UNPARSED_ENTITYDECL",
                          new Object[]{name});
              }
              notation = fEntityScanner.scanName();
              if (notation == null) {
                  reportFatalError("MSG_NOTATION_NAME_REQUIRED_FOR_UNPARSED_ENTITYDECL",
                          new Object[]{name});
              }
          }
  
          // internal entity
          if (systemId == null) {
              scanEntityValue(fLiteral, fLiteral2);
              // since we need it's value anyway, let's snag it so it doesn't get corrupted 
              // if a new load takes place before we store the entity values
              fStringBuffer.clear();
              fStringBuffer2.clear();
              fStringBuffer.append(fLiteral.ch, fLiteral.offset, fLiteral.length);
              fStringBuffer2.append(fLiteral2.ch, fLiteral2.offset, fLiteral2.length);
          }
  
          // skip possible trailing space
          skipSeparator(false, !scanningInternalSubset());
  
          // end
          if (!fEntityScanner.skipChar('>')) {
              reportFatalError("EntityDeclUnterminated", new Object[]{name});
          }
          fMarkUpDepth--;
  
          // register entity and make callback
          if (isPEDecl) {
              name = "%" + name;
          }
          if (systemId != null) {
              String baseSystemId = fEntityScanner.getBaseSystemId();
              if (notation != null) {
                  fEntityManager.addUnparsedEntity(name, publicId, systemId, baseSystemId, notation);
              } else {
                  fEntityManager.addExternalEntity(name, publicId, systemId,
                          baseSystemId);
              }
              if (fDTDHandler != null) {
                  fResourceIdentifier.setValues(publicId, systemId, baseSystemId, XMLEntityManager.expandSystemId(systemId, baseSystemId, false));
                  if (notation != null) {
                      fDTDHandler.unparsedEntityDecl(name, fResourceIdentifier,
                              notation, null);
                  } else {
                      fDTDHandler.externalEntityDecl(name, fResourceIdentifier, null);
                  }
              }
          } else {
              fEntityManager.addInternalEntity(name, fStringBuffer.toString());
              if (fDTDHandler != null) {
                  fDTDHandler.internalEntityDecl(name, fStringBuffer, fStringBuffer2, null);
              }
          }
          fReportEntity = true;
  
      } // scanEntityDecl()
  
      /**
       * Scans an entity value.
       *
       * @param value              The string to fill in with the value.
       * @param nonNormalizedValue The string to fill in with the
       *                           non-normalized value.
       *                           <p/>
       *                           <strong>Note:</strong> This method uses fString, fStringBuffer (through
       *                           the use of scanCharReferenceValue), and fStringBuffer2, anything in them
       *                           at the time of calling is lost.
       */
      protected final void scanEntityValue(XMLString value,
                                           XMLString nonNormalizedValue)
              throws IOException, XNIException {
          int quote = fEntityScanner.scanChar();
          if (quote != '\'' && quote != '"') {
              reportFatalError("OpenQuoteMissingInDecl", null);
          }
          // store at which depth of entities we start
          int entityDepth = fEntityDepth;
  
          XMLString literal = fString;
          XMLString literal2 = fString;
          if (fEntityScanner.scanLiteral(quote, fString) != quote) {
              fStringBuffer.clear();
              fStringBuffer2.clear();
              do {
                  fStringBuffer.append(fString);
                  fStringBuffer2.append(fString);
                  if (fEntityScanner.skipChar('&')) {
                      if (fEntityScanner.skipChar('#')) {
                          fStringBuffer2.append("&#");
                          scanCharReferenceValue(fStringBuffer, fStringBuffer2);
                      } else {
                          fStringBuffer.append('&');
                          fStringBuffer2.append('&');
                          String eName = fEntityScanner.scanName();
                          if (eName == null) {
                              reportFatalError("NameRequiredInReference",
                                      null);
                          } else {
                              fStringBuffer.append(eName);
                              fStringBuffer2.append(eName);
                          }
                          if (!fEntityScanner.skipChar(';')) {
                              reportFatalError("SemicolonRequiredInReference",
                                      new Object[]{eName});
                          } else {
                              fStringBuffer.append(';');
                              fStringBuffer2.append(';');
                          }
                      }
                  } else if (fEntityScanner.skipChar('%')) {
                      while (true) {
                          fStringBuffer2.append('%');
                          String peName = fEntityScanner.scanName();
                          if (peName == null) {
                              reportFatalError("NameRequiredInPEReference",
                                      null);
                          } else if (!fEntityScanner.skipChar(';')) {
                              reportFatalError("SemicolonRequiredInPEReference",
                                      new Object[]{peName});
                          } else {
                              if (scanningInternalSubset()) {
                                  reportFatalError("PEReferenceWithinMarkup",
                                          new Object[]{peName});
                              }
                              fStringBuffer2.append(peName);
                              fStringBuffer2.append(';');
                          }
                          startPE(peName, true);
                          // REVISIT: [Q] Why do we skip spaces here? -Ac
                          // REVISIT: This will make returning the non-
                          //          normalized value harder. -Ac
                          fEntityScanner.skipSpaces();
                          if (!fEntityScanner.skipChar('%'))
                              break;
                      }
                  } else {
                      int c = fEntityScanner.peekChar();
                      if (XMLChar.isHighSurrogate(c)) {
                          scanSurrogates(fStringBuffer2);
                      } else if (isInvalidLiteral(c)) {
                          reportFatalError("InvalidCharInLiteral",
                                  new Object[]{Integer.toHexString(c)});
                          fEntityScanner.scanChar();
                      }
                      // if it's not the delimiting quote or if it is but from a
                      // different entity than the one this literal started from,
                      // simply append the character to our buffer
                      else if (c != quote || entityDepth != fEntityDepth) {
                          fStringBuffer.append((char) c);
                          fStringBuffer2.append((char) c);
                          fEntityScanner.scanChar();
                      }
                  }
              } while (fEntityScanner.scanLiteral(quote, fString) != quote);
              fStringBuffer.append(fString);
              fStringBuffer2.append(fString);
              literal = fStringBuffer;
              literal2 = fStringBuffer2;
          }
          value.setValues(literal);
          nonNormalizedValue.setValues(literal2);
          if (!fEntityScanner.skipChar(quote)) {
              reportFatalError("CloseQuoteMissingInDecl", null);
          }
      } // scanEntityValue(XMLString,XMLString):void
  
      /**
       * Scans a notation declaration
       * <p/>
       * <pre>
       * [82] NotationDecl ::= '&lt;!NOTATION' S Name S (ExternalID|PublicID) S? '>'
       * [83]  PublicID    ::= 'PUBLIC' S PubidLiteral
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!NOTATION'
       */
      private final void scanNotationDecl() throws IOException, XNIException {
  
          // spaces
          fReportEntity = false;
          if (!skipSeparator(true, !scanningInternalSubset())) {
              reportFatalError("MSG_SPACE_REQUIRED_BEFORE_NOTATION_NAME_IN_NOTATIONDECL",
                      null);
          }
  
          // notation name
          String name = null;
          if (fNamespaces) {
              name = fEntityScanner.scanNCName();
          } else {
              name = fEntityScanner.scanName();
          }
          if (name == null) {
              reportFatalError("MSG_NOTATION_NAME_REQUIRED_IN_NOTATIONDECL",
                      null);
          }
  
          // spaces
          if (!skipSeparator(true, !scanningInternalSubset())) {
              // check for invalid ":"
              if (fNamespaces && fEntityScanner.peekChar() == ':') {
                  fEntityScanner.scanChar();
                  XMLStringBuffer colonName = new XMLStringBuffer(name);
                  colonName.append(':');
                  colonName.append(fEntityScanner.scanName());
                  reportFatalError("ColonNotLegalWithNS", new Object[]{colonName.toString()});
                  skipSeparator(true, !scanningInternalSubset());
              } else {
                  reportFatalError("MSG_SPACE_REQUIRED_AFTER_NOTATION_NAME_IN_NOTATIONDECL",
                          new Object[]{name});
              }
          }
  
          // external id
          scanExternalID(fStrings, true);
          String systemId = fStrings[0];
          String publicId = fStrings[1];
          String baseSystemId = fEntityScanner.getBaseSystemId();
  
          if (systemId == null && publicId == null) {
              reportFatalError("ExternalIDorPublicIDRequired",
                      new Object[]{name});
          }
  
          // skip possible trailing space
          skipSeparator(false, !scanningInternalSubset());
  
          // end
          if (!fEntityScanner.skipChar('>')) {
              reportFatalError("NotationDeclUnterminated", new Object[]{name});
          }
          fMarkUpDepth--;
  
          // call handler
          if (fDTDHandler != null) {
              fResourceIdentifier.setValues(publicId, systemId, baseSystemId, XMLEntityManager.expandSystemId(systemId, baseSystemId, false));
              fDTDHandler.notationDecl(name, fResourceIdentifier, null);
          }
          fReportEntity = true;
  
      } // scanNotationDecl()
  
      /**
       * Scans a conditional section. If it's a section to ignore the whole
       * section gets scanned through and this method only returns after the
       * closing bracket has been found. When it's an include section though, it
       * returns to let the main loop take care of scanning it. In that case the
       * end of the section if handled by the main loop (scanDecls).
       * <p/>
       * <pre>
       * [61] conditionalSect   ::= includeSect | ignoreSect
       * [62] includeSect       ::= '&lt;![' S? 'INCLUDE' S? '[' extSubsetDecl ']]>'
       * [63] ignoreSect   ::= '&lt;![' S? 'IGNORE' S? '[' ignoreSectContents* ']]>'
       * [64] ignoreSectContents ::= Ignore ('&lt;![' ignoreSectContents ']]>' Ignore)*
       * [65] Ignore            ::=    Char* - (Char* ('&lt;![' | ']]>') Char*)
       * </pre>
       * <p/>
       * <strong>Note:</strong> Called after scanning past '&lt;!['
       */
      private final void scanConditionalSect(int currPEDepth)
              throws IOException, XNIException {
  
          fReportEntity = false;
          skipSeparator(false, !scanningInternalSubset());
  
          if (fEntityScanner.skipString("INCLUDE")) {
              skipSeparator(false, !scanningInternalSubset());
              if (currPEDepth != fPEDepth && fValidation) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "INVALID_PE_IN_CONDITIONAL",
                          new Object[]{fEntityManager.fCurrentEntity.name},
                          XMLErrorReporter.SEVERITY_ERROR);
              }
              // call handler
              if (!fEntityScanner.skipChar('[')) {
                  reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
              }
  
              if (fDTDHandler != null) {
                  fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_INCLUDE,
                          null);
              }
              fIncludeSectDepth++;
              // just stop there and go back to the main loop
              fReportEntity = true;
          } else if (fEntityScanner.skipString("IGNORE")) {
              skipSeparator(false, !scanningInternalSubset());
              if (currPEDepth != fPEDepth && fValidation) {
                  fErrorReporter.reportError(XMLMessageFormatter.XML_DOMAIN,
                          "INVALID_PE_IN_CONDITIONAL",
                          new Object[]{fEntityManager.fCurrentEntity.name},
                          XMLErrorReporter.SEVERITY_ERROR);
              }
              // call handler
              if (fDTDHandler != null) {
                  fDTDHandler.startConditional(XMLDTDHandler.CONDITIONAL_IGNORE,
                          null);
              }
              if (!fEntityScanner.skipChar('[')) {
                  reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
              }
              fReportEntity = true;
              int initialDepth = ++fIncludeSectDepth;
              if (fDTDHandler != null) {
                  fIgnoreConditionalBuffer.clear();
              }
              while (true) {
                  if (fEntityScanner.skipChar('<')) {
                      if (fDTDHandler != null) {
                          fIgnoreConditionalBuffer.append('<');
                      }
                      //
                      // These tests are split so that we handle cases like
                      // '<<![' and '<!<![' which we might otherwise miss.
                      //
                      if (fEntityScanner.skipChar('!')) {
                          if (fEntityScanner.skipChar('[')) {
                              if (fDTDHandler != null) {
                                  fIgnoreConditionalBuffer.append("![");
                              }
                              fIncludeSectDepth++;
                          } else {
                              if (fDTDHandler != null) {
                                  fIgnoreConditionalBuffer.append("!");
                              }
                          }
                      }
                  } else if (fEntityScanner.skipChar(']')) {
                      if (fDTDHandler != null) {
                          fIgnoreConditionalBuffer.append(']');
                      }
                      //
                      // The same thing goes for ']<![' and '<]]>', etc.
                      //
                      if (fEntityScanner.skipChar(']')) {
                          if (fDTDHandler != null) {
                              fIgnoreConditionalBuffer.append(']');
                          }
                          while (fEntityScanner.skipChar(']')) {
                              /* empty loop body */
                              if (fDTDHandler != null) {
                                  fIgnoreConditionalBuffer.append(']');
                              }
                          }
                          if (fEntityScanner.skipChar('>')) {
                              if (fIncludeSectDepth-- == initialDepth) {
                                  fMarkUpDepth--;
                                  // call handler
                                  if (fDTDHandler != null) {
                                      fLiteral.setValues(fIgnoreConditionalBuffer.ch, 0,
                                              fIgnoreConditionalBuffer.length - 2);
                                      fDTDHandler.ignoredCharacters(fLiteral, null);
                                      fDTDHandler.endConditional(null);
                                  }
                                  return;
                              } else if (fDTDHandler != null) {
                                  fIgnoreConditionalBuffer.append('>');
                              }
                          }
                      }
                  } else {
                      int c = fEntityScanner.scanChar();
                      if (fScannerState == SCANNER_STATE_END_OF_INPUT) {
                          reportFatalError("IgnoreSectUnterminated", null);
                          return;
                      }
                      if (fDTDHandler != null) {
                          fIgnoreConditionalBuffer.append((char) c);
                      }
                  }
              }
          } else {
              reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
          }
  
      } // scanConditionalSect()
  
      /**
       * Dispatch an XML "event".
       *
       * @param complete True if this method is intended to scan
       *                 and dispatch as much as possible.
       * @return True if there is more to scan.
       * @throws IOException  Thrown on i/o error.
       * @throws XNIException Thrown on parse error.
       */
      protected final boolean scanDecls(boolean complete)
              throws IOException, XNIException {
  
          skipSeparator(false, true);
          boolean again = true;
          while (again && fScannerState == SCANNER_STATE_MARKUP_DECL) {
              again = complete;
              if (fEntityScanner.skipChar('<')) {
                  fMarkUpDepth++;
                  if (fEntityScanner.skipChar('?')) {
                      scanPI();
                  } else if (fEntityScanner.skipChar('!')) {
                      if (fEntityScanner.skipChar('-')) {
                          if (!fEntityScanner.skipChar('-')) {
                              reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
                                      null);
                          } else {
                              scanComment();
                          }
                      } else if (fEntityScanner.skipString("ELEMENT")) {
                          scanElementDecl();
                      } else if (fEntityScanner.skipString("ATTLIST")) {
                          scanAttlistDecl();
                      } else if (fEntityScanner.skipString("ENTITY")) {
                          scanEntityDecl();
                      } else if (fEntityScanner.skipString("NOTATION")) {
                          scanNotationDecl();
                      } else if (fEntityScanner.skipChar('[') &&
                              !scanningInternalSubset()) {
                          scanConditionalSect(fPEDepth);
                      } else {
                          fMarkUpDepth--;
                          reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD",
                                  null);
                      }
                  } else {
                      fMarkUpDepth--;
                      reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
                  }
              } else if (fIncludeSectDepth > 0 && fEntityScanner.skipChar(']')) {
                  // end of conditional section?
                  if (!fEntityScanner.skipChar(']')
                          || !fEntityScanner.skipChar('>')) {
                      reportFatalError("IncludeSectUnterminated", null);
                  }
                  // call handler
                  if (fDTDHandler != null) {
                      fDTDHandler.endConditional(null);
                  }
                  // decreaseMarkupDepth();
                  fIncludeSectDepth--;
                  fMarkUpDepth--;
              } else if (scanningInternalSubset() &&
                      fEntityScanner.peekChar() == ']') {
                  // this is the end of the internal subset, let's stop here
                  return false;
              } else if (fEntityScanner.skipSpaces()) {
                  // simply skip
              } else {
                  reportFatalError("MSG_MARKUP_NOT_RECOGNIZED_IN_DTD", null);
                  // Skip the part in error
                  int ch;
                  do {
                      // Ignore the current character
                      fEntityScanner.scanChar();
                      // Skip any separators
                      skipSeparator(false, true);
                      // Keeping getting the next character,
                      // until it's one of the expected ones
                      ch = fEntityScanner.peekChar();
                  } while (ch != '<' && ch != ']' && !XMLChar.isSpace(ch));
              }
              skipSeparator(false, true);
          }
          return fScannerState != SCANNER_STATE_END_OF_INPUT;
      }
  
      /**
       * Skip separator. This is typically just whitespace but it can also be one
       * or more parameter entity references.
       * <p/>
       * If there are some it "expands them" by calling the corresponding entity
       * from the entity manager.
       * <p/>
       * This is recursive and will process has many refs as possible.
       *
       * @param spaceRequired Specify whether some leading whitespace should be
       *                      found
       * @param lookForPERefs Specify whether parameter entity references should
       *                      be looked for
       * @return True if any leading whitespace was found or the end of a
       * parameter entity was crossed.
       */
      private boolean skipSeparator(boolean spaceRequired, boolean lookForPERefs)
              throws IOException, XNIException {
          int depth = fPEDepth;
          boolean sawSpace = fEntityScanner.skipSpaces();
          if (!lookForPERefs || !fEntityScanner.skipChar('%')) {
              return !spaceRequired || sawSpace || (depth != fPEDepth);
          }
          while (true) {
              String name = fEntityScanner.scanName();
              if (name == null) {
                  reportFatalError("NameRequiredInPEReference", null);
              } else if (!fEntityScanner.skipChar(';')) {
                  reportFatalError("SemicolonRequiredInPEReference",
                          new Object[]{name});
              }
              startPE(name, false);
              fEntityScanner.skipSpaces();
              if (!fEntityScanner.skipChar('%'))
                  return true;
          }
      }
  
  
      /*
       * Element Children Content Stack
       */
      private final void pushContentStack(int c) {
          if (fContentStack.length == fContentDepth) {
              int[] newStack = new int[fContentDepth * 2];
              System.arraycopy(fContentStack, 0, newStack, 0, fContentDepth);
              fContentStack = newStack;
          }
          fContentStack[fContentDepth++] = c;
      }
  
      private final int popContentStack() {
          return fContentStack[--fContentDepth];
      }
  
  
      /*
       * Parameter Entity Stack
       */
      private final void pushPEStack(int depth, boolean report) {
          if (fPEStack.length == fPEDepth) {
              int[] newIntStack = new int[fPEDepth * 2];
              System.arraycopy(fPEStack, 0, newIntStack, 0, fPEDepth);
              fPEStack = newIntStack;
              // report end/start calls
              boolean[] newBooleanStack = new boolean[fPEDepth * 2];
              System.arraycopy(fPEReport, 0, newBooleanStack, 0, fPEDepth);
              fPEReport = newBooleanStack;
  
          }
          fPEReport[fPEDepth] = report;
          fPEStack[fPEDepth++] = depth;
      }
  
      /**
       * pop the stack
       */
      private final int popPEStack() {
          return fPEStack[--fPEDepth];
      }
  
      /**
       * look at the top of the stack
       */
      private final boolean peekReportEntity() {
          return fPEReport[fPEDepth - 1];
      }
  
  
      /*
       * Utility method
       */
      private final void ensureEnumerationSize(int size) {
          if (fEnumeration.length == size) {
              String[] newEnum = new String[size * 2];
              System.arraycopy(fEnumeration, 0, newEnum, 0, size);
              fEnumeration = newEnum;
          }
      }
  
      // private methods
      private void init() {
          // reset state related data
          fStartDTDCalled = false;
          fExtEntityDepth = 0;
          fIncludeSectDepth = 0;
          fMarkUpDepth = 0;
          fPEDepth = 0;
  
          fStandalone = false;
          fSeenExternalDTD = false;
          fSeenPEReferences = false;
  
          // set starting state
          setScannerState(SCANNER_STATE_TEXT_DECL);
      }
  
      private int skipInvalidEnumerationValue() throws IOException {
          int c;
          do {
              c = fEntityScanner.scanChar();
          }
          while (c != '|' && c != ')');
          ensureEnumerationSize(fEnumerationCount + 1);
          fEnumeration[fEnumerationCount++] = XMLSymbols.EMPTY_STRING;
          return c;
      }
  
  } // class XMLDTDScannerImpl
  