// Copyright (c) 1994, 1995 James Clark
// See the file COPYING for copying permission.

// Prolog, dtd and declaration parsing.

#include "splib.h"
#include "Parser.h"
#include "Param.h"
#include "Markup.h"
#include "ParserMessages.h"
#include "MessageArg.h"
#include "TokenMessageArg.h"
#include "token.h"
#include "macros.h"

#ifdef SP_NAMESPACE
namespace SP_NAMESPACE {
#endif

static const AllowedParams allowMdc(Param::mdc);
static const AllowedParams allowName(Param::name);
static const AllowedParams allowParamLiteral(Param::paramLiteral);
static const AllowedParams allowNameNameGroup(Param::name, Param::nameGroup);
static const AllowedParams allowDsoMdc(Param::dso, Param::mdc);
static AllowedParams allowNameMdc(Param::name, Param::mdc);
static AllowedParams
  allowExplicitLinkRuleMdc(Param::mdc,
			   Param::name,
			   Param::nameGroup,
			   Param::indicatedReservedName + Syntax::rIMPLIED);
static AllowedParams
  allowNameNameGroupMdc(Param::name, Param::nameGroup, Param::mdc);

static const AllowedParams
  allowLinkSetSpec(Param::name,
		   Param::indicatedReservedName + Syntax::rINITIAL,
		   Param::indicatedReservedName + Syntax::rEMPTY,
		   Param::indicatedReservedName + Syntax::rRESTORE);

void Parser::doProlog()
{
  const unsigned maxTries = 10;
  unsigned tries = 0;
  do {
    if (cancelled()) {
      allDone();
      return;
    }
    Token token = getToken(proMode);
    switch (token) {
    case tokenUnrecognized:
      if (reportNonSgmlCharacter())
	break;
      if (hadDtd()) {
	currentInput()->ungetToken();
	endProlog();
	return;
      }
      {
	StringC gi;
	if (lookingAtStartTag(gi)) {
	  currentInput()->ungetToken();
	  implyDtd(gi);
	  return;
	}
      }
	    
      if (++tries >= maxTries) {
	message(ParserMessages::notSgml);
	giveUp();
	return;
      }
      message(ParserMessages::prologCharacter, StringMessageArg(currentToken()));
      prologRecover();
      break;
    case tokenEe:
      if (hadDtd()) {
	endProlog();
	return;
      }
      message(ParserMessages::documentEndProlog);
      allDone();
      return;
    case tokenMdoMdc:
      // empty comment
      emptyCommentDecl();
      break;
    case tokenMdoCom:
      if (!parseCommentDecl())
	prologRecover();
      break;
    case tokenMdoNameStart:
      setPass2Start();
      if (startMarkup(eventsWanted().wantPrologMarkup(), currentLocation()))
	currentMarkup()->addDelim(Syntax::dMDO);
      Syntax::ReservedName name;
      if (parseDeclarationName(&name)) {
	switch (name) {
	case Syntax::rDOCTYPE:
	  if (!parseDoctypeDeclStart())
	    giveUp();
	  return;
	case Syntax::rLINKTYPE:
	  if (!parseLinktypeDeclStart())
	    giveUp();
	  return;
	case Syntax::rELEMENT:
	case Syntax::rATTLIST:
	case Syntax::rENTITY:
	case Syntax::rNOTATION:
	case Syntax::rSHORTREF:
	case Syntax::rUSEMAP:
	case Syntax::rUSELINK:
	case Syntax::rLINK:
	case Syntax::rIDLINK:
	  message(ParserMessages::prologDeclaration,
		  StringMessageArg(syntax().reservedName(name)));
	  if (!hadDtd())
	    tries++;
	  prologRecover();
	  break;
	default:
	  message(ParserMessages::noSuchDeclarationType,
		  StringMessageArg(syntax().reservedName(name)));
	  prologRecover();
	  break;
	}
      }
      else
	prologRecover();
      break;
    case tokenPio:
      if (!parseProcessingInstruction())
	prologRecover();
      break;
    case tokenS:
      if (eventsWanted().wantPrologMarkup()) {
	extendS();
	eventHandler().sSep(new (eventAllocator())
			    SSepEvent(currentInput()->currentTokenStart(),
				      currentInput()->currentTokenLength(),
				      currentLocation(),
				      1));
      }
      break;
    default:
      CANNOT_HAPPEN();
    }
  } while (eventQueueEmpty());
}

void Parser::endProlog()
{
  if (baseDtd().isNull()
#if 0
      || baseDtd()->documentElementType()->definition()->undefined()
#endif
      ) {
    // We could continue, but there's not a lot of point.
    giveUp();
    return;
  }
  if (maybeStartPass2())
    setPhase(prologPhase);
  else {
    if (inputLevel() == 0) {
      allDone();
      return;
    }
    if (pass2())
      checkEntityStability();
    setPhase(instanceStartPhase);
    startInstance();
    ConstPtr<ComplexLpd> lpd;
    Vector<AttributeList> simpleLinkAtts;
    Vector<StringC> simpleLinkNames;
    for (size_t i = 0; i < nActiveLink(); i++)
      if (activeLpd(i).type() == Lpd::simpleLink) {
	const SimpleLpd &lpd = (SimpleLpd &)activeLpd(i);
	simpleLinkNames.push_back(lpd.name());
	simpleLinkAtts.resize(simpleLinkAtts.size() + 1);
	simpleLinkAtts.back().init(lpd.attributeDef());
	simpleLinkAtts.back().finish(*this);
      }
      else 
	lpd = (ComplexLpd *)&activeLpd(i);
    eventHandler().endProlog(new (eventAllocator())
			     EndPrologEvent(baseDtd(),
					    lpd,
					    simpleLinkNames,
					    simpleLinkAtts,
					    currentLocation()));
  }
}

void Parser::prologRecover()
{
  unsigned skipCount = 0;
  const unsigned skipMax = 250;
  for (;;) {
    Token token = getToken(proMode);
    skipCount++;
    if (token == tokenUnrecognized) {
      token = getToken(mdMode);
      if (token == tokenMdc) {
	token = getToken(proMode);
	if (token == tokenS)
	  return;
      }
    }
    switch (token) {
    case tokenUnrecognized:
      (void)getChar();
      break;
    case tokenEe:
      return;
    case tokenMdoMdc:
    case tokenMdoCom:
    case tokenMdoNameStart:
    case tokenPio:
      currentInput()->ungetToken();
      return;
    case tokenS:
      if (currentChar() == syntax().standardFunction(Syntax::fRE)
	  && skipCount >= skipMax)
	return;
    default:
      break;
    }
  }
}

void Parser::doDeclSubset()
{
  do {
    if (cancelled()) {
      allDone();
      return;
    }
    Token token = getToken(currentMode());
    unsigned startLevel = inputLevel();
    Boolean inDtd = !haveDefLpd();
    switch (token) {
    case tokenUnrecognized:
      if (reportNonSgmlCharacter())
	break;
      message(ParserMessages::declSubsetCharacter, StringMessageArg(currentToken()));
      declSubsetRecover(startLevel);
      break;
    case tokenEe:
      if (inputLevel() == specialParseInputLevel()) {
	// FIXME have separate messages for each type of special parse
	message(ParserMessages::specialParseEntityEnd);
      }
      if (eventsWanted().wantPrologMarkup())
	eventHandler().entityEnd(new (eventAllocator())
				 EntityEndEvent(currentLocation()));
      if (inputLevel() == 2) {
	const Entity *e
	  = currentLocation().origin()->entity();
	if (e
	    && (e->declType() == Entity::doctype
		|| e->declType() == Entity::linktype)) {
	  // popInputStack may destroy e
	  Boolean fake = e->defLocation().origin().isNull();
	  popInputStack();
	  if (!(inDtd
		? parseDoctypeDeclEnd(fake)
		: parseLinktypeDeclEnd()))
	    ;			// FIXME recover
	  setPhase(prologPhase);
	  return;
	}
      }
      if (inputLevel() == 1) {
	if (finalPhase() == declSubsetPhase) {
	  checkDtd(defDtd());
	  endDtd();
	}
	else
	  // Give message before popping stack.
	  message(inDtd
		  ? ParserMessages::documentEndDtdSubset
		  : ParserMessages::documentEndLpdSubset);
	popInputStack();
	allDone();
      }
      else
	popInputStack();
      return;
    case tokenDsc:		// end of declaration subset
      // FIXME what's the right location?
      if (!referenceDsEntity(currentLocation())) {
	if (!(inDtd ? parseDoctypeDeclEnd() : parseLinktypeDeclEnd()))
	  ;			// FIXME recover
	setPhase(prologPhase);
      }
      return;
    case tokenMdoNameStart:	// named markup declaration
      if (startMarkup(eventsWanted().wantPrologMarkup(), currentLocation()))
	currentMarkup()->addDelim(Syntax::dMDO);
      Syntax::ReservedName name;
      Boolean result;
      if (parseDeclarationName(&name,
			       inDtd && !options().errorAfdr)) {
	switch (name) {
	case Syntax::rANY:	// used for <!AFDR
	  result = parseAfdrDecl();
	  break;
	case Syntax::rELEMENT:
	  if (inDtd)
	    result = parseElementDecl();
	  else {
	    message(ParserMessages::lpdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	    result = 0;
	  }
	  break;
	case Syntax::rATTLIST:
	  result = parseAttlistDecl();
	  break;
	case Syntax::rENTITY:
	  result = parseEntityDecl();
	  break;
	case Syntax::rNOTATION:
	  result = parseNotationDecl();
	  if (!inDtd && !sd().www())
	    message(ParserMessages::lpdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	  break;
	case Syntax::rSHORTREF:
	  if (inDtd)
	    result = parseShortrefDecl();
	  else {
	    message(ParserMessages::lpdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	    result = 0;
	  }
	  break;
	case Syntax::rUSEMAP:
	  if (inDtd)
	    result = parseUsemapDecl();
	  else {
	    message(ParserMessages::lpdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	    result = 0;
	  }
	  break;
	case Syntax::rLINK:
	  if (inDtd) {
	    message(ParserMessages::dtdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	    result = 0;
	  }
	  else
	    result = parseLinkDecl();
	  break;
	case Syntax::rIDLINK:
	  if (inDtd) {
	    message(ParserMessages::dtdSubsetDeclaration,
		    StringMessageArg(syntax().reservedName(name)));
	    result = 0;
	  }
	  else
	    result = parseIdlinkDecl();
	  break;
	case Syntax::rDOCTYPE:
	case Syntax::rLINKTYPE:
	case Syntax::rUSELINK:
	  result = 0;
	  message(inDtd
		  ? ParserMessages::dtdSubsetDeclaration
		  : ParserMessages::lpdSubsetDeclaration,
		  StringMessageArg(syntax().reservedName(name)));
	  break;
	default:
	  result = 0;
	  message(ParserMessages::noSuchDeclarationType,
		  StringMessageArg(syntax().reservedName(name)));
	  break;
	}
      }
      else
	result = 0;
      if (!result)
	declSubsetRecover(startLevel);
      break;
    case tokenMdoMdc:		// empty comment declaration
      // empty comment
      emptyCommentDecl();
      break;
    case tokenMdoCom:		// comment declaration
      if (!parseCommentDecl())
	declSubsetRecover(startLevel);
      break;
    case tokenMdoDso:		// marked section declaration
      if (!parseMarkedSectionDeclStart())
	declSubsetRecover(startLevel);
      break;
    case tokenMscMdc:
      handleMarkedSectionEnd();
      break;
    case tokenPeroGrpo:		// parameter entity reference with name group
      message(ParserMessages::peroGrpoProlog);
      // fall through
    case tokenPeroNameStart:	// parameter entity reference
      {
	ConstPtr<Entity> entity;
	Ptr<EntityOrigin> origin;
	if (parseEntityReference(1, token == tokenPeroGrpo, entity, origin)) {
	  if (!entity.isNull())
	    entity->dsReference(*this, origin);
	}
	else
	  declSubsetRecover(startLevel);
      }
      break;
    case tokenPio:		// processing instruction
      if (!parseProcessingInstruction())
	declSubsetRecover(startLevel);
      break;
    case tokenS:		// white space
      if (eventsWanted().wantPrologMarkup()) {
	extendS();
	eventHandler().sSep(new (eventAllocator())
			    SSepEvent(currentInput()->currentTokenStart(),
				      currentInput()->currentTokenLength(),
				      currentLocation(),
				      1));
      }
      break;
    case tokenIgnoredChar:
      // from an ignored marked section
      if (eventsWanted().wantPrologMarkup())
	eventHandler().ignoredChars(new (eventAllocator())
				    IgnoredCharsEvent(currentInput()->currentTokenStart(),
						      currentInput()->currentTokenLength(),
						      currentLocation(),
						      1));
      break;
    case tokenRe:
    case tokenRs:
    case tokenCroNameStart:
    case tokenCroDigit:
    case tokenHcroHexDigit:
    case tokenEroNameStart:
    case tokenEroGrpo:
    case tokenChar:
      // these can occur in a cdata or rcdata marked section
      message(ParserMessages::dataMarkedSectionDeclSubset);
      declSubsetRecover(startLevel);
      break;
    default:
      CANNOT_HAPPEN();
    }
  } while (eventQueueEmpty());
}

void Parser::declSubsetRecover(unsigned startLevel)
{
  for (;;) {
    Token token = getToken(currentMode());
    switch (token) {
    case tokenUnrecognized:
      (void)getChar();
      break;
    case tokenEe:
      if (inputLevel() <= startLevel)
	return;
      popInputStack();
      break;
    case tokenMdoCom:
    case tokenDsc:
    case tokenMdoNameStart:
    case tokenMdoMdc:
    case tokenMdoDso:
    case tokenMscMdc:
    case tokenPio:
      if (inputLevel() == startLevel) {
	currentInput()->ungetToken();
	return;
      }
      break;
    default:
      break;
    }
  }
}

Boolean Parser::lookingAtStartTag(StringC &gi)
{
  // This is harder than might be expected since we may not have compiled
  // the recognizers for the instance yet.
  const StringC &stago = instanceSyntax().delimGeneral(Syntax::dSTAGO);
  for (size_t i = currentInput()->currentTokenLength();
       i < stago.size();
       i++)
    if (currentInput()->tokenChar(messenger()) == InputSource::eE)
      return 0;
  StringC delim;
  getCurrentToken(instanceSyntax().generalSubstTable(), delim);
  if (delim != stago)
    return 0;
  Xchar c = currentInput()->tokenChar(messenger());
  if (!instanceSyntax().isNameStartCharacter(c))
    return 0;
  do {
    gi += (*instanceSyntax().generalSubstTable())[(Char)c];
    c = currentInput()->tokenChar(messenger());
  } while (instanceSyntax().isNameCharacter(c));
  return 1;
}

Boolean Parser::parseDeclarationName(Syntax::ReservedName *result,
				     Boolean allowAfdr)
{
  currentInput()->discardInitial();
  extendNameToken(syntax().namelen(), ParserMessages::nameLength);
  StringC &name = nameBuffer();
  getCurrentToken(syntax().generalSubstTable(), name);
  if (!syntax().lookupReservedName(name, result)) {
    if (allowAfdr && name == sd().execToInternal("AFDR")) {
      *result = Syntax::rANY;
      if (currentMarkup())
	currentMarkup()->addName(currentInput());
    }
    else {
      message(ParserMessages::noSuchDeclarationType, StringMessageArg(name));
      return 0;
    }
  }
  else if (currentMarkup())
    currentMarkup()->addReservedName(*result, currentInput());
  return 1;
}

Boolean Parser::parseElementDecl()
{
  unsigned declInputLevel = inputLevel();
  Param parm;
  if (!parseParam(allowNameNameGroup, declInputLevel, parm))
    return 0;
  Vector<NameToken> nameVector;
  if (parm.type == Param::nameGroup) {
    parm.nameTokenVector.swap(nameVector);
    if (options().warnElementGroupDecl)
      message(ParserMessages::elementGroupDecl);
  }
  else {
    nameVector.resize(1);
    parm.token.swap(nameVector[0].name);
  }
  static AllowedParams
    allowRankOmissionContent(Param::number,
			     Param::reservedName + Syntax::rO,
			     Param::minus,
			     Param::reservedName + Syntax::rCDATA,
			     Param::reservedName + Syntax::rRCDATA,
			     Param::reservedName + Syntax::rEMPTY,
			     Param::reservedName + Syntax::rANY,
			     Param::modelGroup);
  if (!parseParam(allowRankOmissionContent, declInputLevel, parm))
    return 0;
  StringC rankSuffix;
  Vector<ElementType *> elements(nameVector.size());
  Vector<RankStem *> rankStems;
  Vector<const RankStem *> constRankStems;
  size_t i;
  if (parm.type == Param::number) {
    if (options().warnRank)
      message(ParserMessages::rank);
    parm.token.swap(rankSuffix);
    rankStems.resize(nameVector.size());
    constRankStems.resize(nameVector.size());
    for (i = 0; i < elements.size(); i++) {
      StringC name(nameVector[i].name);
      name += rankSuffix;
      if (name.size() > syntax().namelen()
	  && nameVector[i].name.size() <= syntax().namelen())
	message(ParserMessages::genericIdentifierLength,
		NumberMessageArg(syntax().namelen()));
      elements[i] = lookupCreateElement(name);
      rankStems[i] = lookupCreateRankStem(nameVector[i].name);
      constRankStems[i] = rankStems[i];
    }
    static AllowedParams
      allowOmissionContent(Param::reservedName + Syntax::rO,
			   Param::minus,
			   Param::reservedName + Syntax::rCDATA,
			   Param::reservedName + Syntax::rRCDATA,
			   Param::reservedName + Syntax::rEMPTY,
			   Param::reservedName + Syntax::rANY,
			   Param::modelGroup);
    Token token = getToken(mdMinusMode);
    if (token == tokenNameStart)
      message(ParserMessages::psRequired);
    currentInput()->ungetToken();
    if (!parseParam(allowOmissionContent, declInputLevel, parm))
      return 0;
  }
  else {
    for (i = 0; i < elements.size(); i++)
      elements[i] = lookupCreateElement(nameVector[i].name);
  }
  for (i = 0; i < elements.size(); i++)
    if (defDtd().lookupRankStem(elements[i]->name()) && validate())
      message(ParserMessages::rankStemGenericIdentifier,
	      StringMessageArg(elements[i]->name()));
  unsigned char omitFlags = 0;
  if (parm.type == Param::minus
      || parm.type == Param::reservedName + Syntax::rO) {
    if (options().warnMinimizationParam)
      message(ParserMessages::minimizationParam);
    omitFlags |= ElementDefinition::omitSpec;
    if (parm.type != Param::minus)
      omitFlags |= ElementDefinition::omitStart;
    static AllowedParams allowOmission(Param::reservedName + Syntax::rO,
				       Param::minus);
    if (!parseParam(allowOmission, declInputLevel, parm))
      return 0;
    if (parm.type != Param::minus)
      omitFlags |= ElementDefinition::omitEnd;
    static AllowedParams allowContent(Param::reservedName + Syntax::rCDATA,
				      Param::reservedName + Syntax::rRCDATA,
				      Param::reservedName + Syntax::rEMPTY,
				      Param::reservedName + Syntax::rANY,
				      Param::modelGroup);
    if (!parseParam(allowContent, declInputLevel, parm))
      return 0;
  }
  else {
    if (sd().omittag())
      message(ParserMessages::missingTagMinimization);
  }
  Ptr<ElementDefinition> def;
  switch (parm.type) {
  case Param::reservedName + Syntax::rCDATA:
    def = new ElementDefinition(markupLocation(),
				defDtd().allocElementDefinitionIndex(),
				omitFlags,
				ElementDefinition::cdata);
    if (!parseParam(allowMdc, declInputLevel, parm))
      return 0;
    if (options().warnCdataContent)
      message(ParserMessages::cdataContent);
    break;
  case Param::reservedName + Syntax::rRCDATA:
    def = new ElementDefinition(markupLocation(),
				defDtd().allocElementDefinitionIndex(),
				omitFlags,
				ElementDefinition::rcdata);
    if (!parseParam(allowMdc, declInputLevel, parm))
      return 0;
    if (options().warnRcdataContent)
      message(ParserMessages::rcdataContent);
    break;
  case Param::reservedName + Syntax::rEMPTY:
    def = new ElementDefinition(markupLocation(),
				defDtd().allocElementDefinitionIndex(),
				omitFlags,
				ElementDefinition::empty);
    if ((omitFlags & ElementDefinition::omitSpec)
	&& !(omitFlags & ElementDefinition::omitEnd)
	&& options().warnShould)
      message(ParserMessages::emptyOmitEndTag);
    if (!parseParam(allowMdc, declInputLevel, parm))
      return 0;
    break;
  case Param::reservedName + Syntax::rANY:
    def = new ElementDefinition(markupLocation(),
				defDtd().allocElementDefinitionIndex(),
				omitFlags,
				ElementDefinition::any);
    if (!parseExceptions(declInputLevel, def))
      return 0;
    break;
  case Param::modelGroup:
    {
      unsigned long cnt = parm.modelGroupPtr->grpgtcnt();
      // The outermost model group isn't formally a content token.
      if (cnt - 1 > syntax().grpgtcnt())
	message(ParserMessages::grpgtcnt, NumberMessageArg(syntax().grpgtcnt()));
      Owner<CompiledModelGroup>
	modelGroup(new CompiledModelGroup(parm.modelGroupPtr));
      Vector<ContentModelAmbiguity> ambiguities;
      Boolean pcdataUnreachable;
      modelGroup->compile(currentDtd().nElementTypeIndex(), ambiguities,
			  pcdataUnreachable);
      if (pcdataUnreachable && options().warnMixedContent)
	message(ParserMessages::pcdataUnreachable);
      if (validate()) {
	for (i = 0; i < ambiguities.size(); i++) {
	  const ContentModelAmbiguity &a = ambiguities[i];
	  reportAmbiguity(a.from, a.to1, a.to2, a.andDepth);
	}
      }
      def = new ElementDefinition(markupLocation(),
				  defDtd().allocElementDefinitionIndex(),
				  omitFlags,
				  ElementDefinition::modelGroup,
				  modelGroup);
      if (!parseExceptions(declInputLevel, def))
	return 0;
    }
    break;
  }
  if (rankSuffix.size() > 0)
    def->setRank(rankSuffix, constRankStems);
  ConstPtr<ElementDefinition> constDef(def);
  for (i = 0; i < elements.size(); i++) {
    if (elements[i]->definition() != 0) {
      if (validate())
	message(ParserMessages::duplicateElementDefinition,
	        StringMessageArg(elements[i]->name()));
    }
    else {
      elements[i]->setElementDefinition(constDef, i);
      if (!elements[i]->attributeDef().isNull())
	checkElementAttribute(elements[i]);
    }
    if (rankStems.size() > 0)
      rankStems[i]->addDefinition(constDef);
  }
  if (currentMarkup()) {
    Vector<const ElementType *> v(elements.size());
    for (i = 0; i < elements.size(); i++)
      v[i] = elements[i];
    eventHandler().elementDecl(new (eventAllocator())
			       ElementDeclEvent(v, currentDtdPointer(),
						markupLocation(),
						currentMarkup()));
  }
  return 1;
}

void Parser::reportAmbiguity(const LeafContentToken *from,
			     const LeafContentToken *to1,
			     const LeafContentToken *to2,
			     unsigned ambigAndDepth)
{
  StringC toName;
  const ElementType *toType = to1->elementType();
  if (toType)
    toName = toType->name();
  else {
    toName = syntax().delimGeneral(Syntax::dRNI);
    toName += syntax().reservedName(Syntax::rPCDATA);
  }
  unsigned to1Index = to1->typeIndex() + 1;
  unsigned to2Index = to2->typeIndex() + 1;
  if (from->isInitial())
    message(ParserMessages::ambiguousModelInitial,
	    StringMessageArg(toName),
	    OrdinalMessageArg(to1Index),
	    OrdinalMessageArg(to2Index));
  else {
    StringC fromName;
    const ElementType *fromType = from->elementType();
    if (fromType)
      fromName = fromType->name();
    else {
      fromName = syntax().delimGeneral(Syntax::dRNI);
      fromName += syntax().reservedName(Syntax::rPCDATA);
    }
    unsigned fromIndex = from->typeIndex() + 1;
    unsigned andMatches = from->andDepth() - ambigAndDepth;
    if (andMatches == 0)
      message(ParserMessages::ambiguousModel,
	      StringMessageArg(fromName),
	      OrdinalMessageArg(fromIndex),
	      StringMessageArg(toName),
	      OrdinalMessageArg(to1Index),
	      OrdinalMessageArg(to2Index));
    else if (andMatches == 1)
      message(ParserMessages::ambiguousModelSingleAnd,
	      StringMessageArg(fromName),
	      OrdinalMessageArg(fromIndex),
	      StringMessageArg(toName),
	      OrdinalMessageArg(to1Index),
	      OrdinalMessageArg(to2Index));
    else
      message(ParserMessages::ambiguousModelMultipleAnd,
	      StringMessageArg(fromName),
	      OrdinalMessageArg(fromIndex),
	      NumberMessageArg(andMatches),
	      StringMessageArg(toName),
	      OrdinalMessageArg(to1Index),
	      OrdinalMessageArg(to2Index));
  }
}


// Check the compatibility of the attribute definition with
// the element definition.

void Parser::checkElementAttribute(const ElementType *e, size_t checkFrom)
{
  if (!validate())
    return;
  const AttributeDefinitionList *attDef = e->attributeDef().pointer();
  Boolean conref = 0;
  ASSERT(e != 0);
  const ElementDefinition *edef = e->definition();
  ASSERT(edef != 0);
  ASSERT(attDef != 0);
  size_t attDefLength = attDef->size();
  for (size_t i = checkFrom; i < attDefLength; i++) {
    const AttributeDefinition *p = attDef->def(i);
    if (p->isConref())
      conref = 1;
    if (p->isNotation()
	&& edef->declaredContent() == ElementDefinition::empty)
      message(ParserMessages::notationEmpty, StringMessageArg(e->name()));
  }
  if (conref) {
#if 0
    if (edef->omittedTagSpec() && !edef->canOmitEndTag()
	&& options().warnShould)
      message(ParserMessages::conrefOmitEndTag, StringMessageArg(e->name()));
#endif
    if (edef->declaredContent() == ElementDefinition::empty)
      message(ParserMessages::conrefEmpty, StringMessageArg(e->name()));
  }
}

ElementType *Parser::lookupCreateElement(const StringC &name)
{
  ElementType *e = defDtd().lookupElementType(name);
  if (!e) {
    if (haveDefLpd()) 
      message(ParserMessages::noSuchSourceElement, StringMessageArg(name));
    else {
      e = new ElementType(name, defDtd().allocElementTypeIndex());
      defDtd().insertElementType(e);
    }
  }
  return e;
}

RankStem *Parser::lookupCreateRankStem(const StringC &name)
{
  RankStem *r = defDtd().lookupRankStem(name);
  if (!r) {
    r = new RankStem(name, defDtd().nRankStem());
    defDtd().insertRankStem(r);
    const ElementType *e = defDtd().lookupElementType(name);
    if (e && e->definition() != 0)
      message(ParserMessages::rankStemGenericIdentifier, StringMessageArg(name));
  }
  return r;
}

Boolean Parser::parseExceptions(unsigned declInputLevel,
				Ptr<ElementDefinition> &def)
{
  Param parm;
  static AllowedParams
    allowExceptionsMdc(Param::mdc, Param::exclusions, Param::inclusions);
  if (!parseParam(allowExceptionsMdc, declInputLevel, parm))
    return 0;
  if (parm.type == Param::exclusions) {
    if (options().warnExclusion)
      message(ParserMessages::exclusion);
    def->setExclusions(parm.elementVector);
    static AllowedParams allowInclusionsMdc(Param::mdc, Param::inclusions);
    if (!parseParam(allowInclusionsMdc, declInputLevel, parm))
      return 0;
  }
  if (parm.type == Param::inclusions) {
    if (options().warnInclusion)
      message(ParserMessages::inclusion);
    def->setInclusions(parm.elementVector);
    size_t nI = def->nInclusions();
    size_t nE = def->nExclusions();
    if (nE) {
      for (size_t i = 0; i < nI; i++) {
	const ElementType *e = def->inclusion(i);
	for (size_t j = 0; j < nE; j++)
	  if (def->exclusion(j) == e)
	    message(ParserMessages::excludeIncludeSame,
		    StringMessageArg(e->name()));
      }
    }
    if (!parseParam(allowMdc, declInputLevel, parm))
      return 0;
  }
  return 1;
}

Boolean Parser::parseAttlistDecl()
{
  unsigned declInputLevel = inputLevel();
  Param parm;
  size_t attcnt = 0;
  size_t idIndex = size_t(-1);
  size_t notationIndex = size_t(-1);
  Boolean anyCurrent = 0;
  
  Boolean isNotation;
  Vector<Attributed *> attributed;
  if (!parseAttributed(declInputLevel, parm, attributed, isNotation))
    return 0;
  Vector<CopyOwner<AttributeDefinition> > defs;
  if (!parseParam(sd().www() ? allowNameMdc : allowName, declInputLevel, parm))
    return 0;
  while (parm.type != Param::mdc) {
    StringC attributeName;
    parm.token.swap(attributeName);
    attcnt++;
    Boolean duplicate = 0;
    size_t i;
    for (i = 0; i < defs.size(); i++)
      if (defs[i]->name() == attributeName) {
	message(ParserMessages::duplicateAttributeDef,
		StringMessageArg(attributeName));
	duplicate = 1;
	break;
      }
    Owner<DeclaredValue> declaredValue;
    if (!parseDeclaredValue(declInputLevel, isNotation, parm, declaredValue))
      return 0;
    if (!duplicate) {
      if (declaredValue->isId()) {
	if (idIndex != size_t(-1))
	  message(ParserMessages::multipleIdAttributes,
		  StringMessageArg(defs[idIndex]->name()));
	idIndex = defs.size();
      }
      else if (declaredValue->isNotation()) {
	if (notationIndex != size_t(-1))
	  message(ParserMessages::multipleNotationAttributes,
		  StringMessageArg(defs[notationIndex]->name()));
	notationIndex = defs.size();
      }
    }
    const Vector<StringC> *tokensPtr = declaredValue->getTokens();
    if (tokensPtr) {
      size_t nTokens = tokensPtr->size();
      if (!sd().www()) {
	Vector<StringC>::const_iterator tokens = tokensPtr->begin();
	for (i = 0; i < nTokens; i++) {
	  for (size_t j = 0; j < defs.size(); j++)
	    if (defs[j]->containsToken(tokens[i])) {
	      message(ParserMessages::duplicateAttributeToken,
		      StringMessageArg(tokens[i]));
	      break;
	    }
	}
      }
      attcnt += nTokens;
    }
    Owner<AttributeDefinition> def;
    if (!parseDefaultValue(declInputLevel, isNotation, parm, attributeName,
			   declaredValue, def, anyCurrent))
      return 0;
    if (haveDefLpd() && defLpd().type() == Lpd::simpleLink && !def->isFixed())
      message(ParserMessages::simpleLinkFixedAttribute);
    if (!duplicate) {
      defs.resize(defs.size() + 1);
      defs.back() = def.extract();
    }
    static AllowedParams allowNameMdc(Param::name, Param::mdc);
    if (!parseParam(allowNameMdc, declInputLevel, parm))
      return 0;
  }
  if (attcnt > syntax().attcnt())
    message(ParserMessages::attcnt,
	    NumberMessageArg(attcnt),
	    NumberMessageArg(syntax().attcnt()));
  if (haveDefLpd() && !isNotation) {
    if (defLpd().type() == Lpd::simpleLink) {
      for (size_t i = 0; i < attributed.size(); i++) {
	const ElementType *e = (const ElementType *)attributed[i];
	if (e) {
	  if (e->name() == defLpd().sourceDtd()->name()) {
	    SimpleLpd &lpd = (SimpleLpd &)defLpd();
	    if (lpd.attributeDef().isNull())
	      lpd.setAttributeDef(new AttributeDefinitionList(defs, 0));
	    else
	      message(ParserMessages::duplicateAttlistElement,
		      StringMessageArg(e->name()));
	  }
	  else
	    message(ParserMessages::simpleLinkAttlistElement,
		    StringMessageArg(e->name()));
	}
      }
    }
    else {
      Ptr<AttributeDefinitionList>
	adl(new AttributeDefinitionList(defs,
					defComplexLpd()
					.allocAttributeDefinitionListIndex()));
      for (size_t i = 0; i < attributed.size(); i++) {
	const ElementType *e = (const ElementType *)attributed[i];
	if (e) {
	  if (defComplexLpd().attributeDef(e).isNull())
	    defComplexLpd().setAttributeDef(e, adl);
	  else
	    message(ParserMessages::duplicateAttlistElement,
		    StringMessageArg(e->name()));
	}
      }
    }
  }
  else {
    Ptr<AttributeDefinitionList>
      adl(new AttributeDefinitionList(defs,
				      defDtd()
				      .allocAttributeDefinitionListIndex(),
				      anyCurrent,
				      idIndex,
				      notationIndex));
    for (size_t i = 0; i < attributed.size(); i++) {
      if (attributed[i]->attributeDef().isNull()) {
	attributed[i]->setAttributeDef(adl);
	if (!isNotation) {
	  ElementType *e = (ElementType *)attributed[i];
	  if (e->definition() != 0)
	    checkElementAttribute(e);
	}
      }
      else if (options().errorAfdr && !sd().www()) {
	if (isNotation)
	  message(ParserMessages::duplicateAttlistNotation,
		  StringMessageArg(((Notation *)attributed[i])->name()));
	else
	  message(ParserMessages::duplicateAttlistElement,
		  StringMessageArg(((ElementType *)attributed[i])->name()));
      }
      else {
	if (!hadAfdrDecl() && !sd().www()) {
	  message(ParserMessages::missingAfdrDecl);
	  setHadAfdrDecl();
	}
	AttributeDefinitionList *curAdl;
	{
	  // Use block to make sure temporary gets destroyed.
	  curAdl = attributed[i]->attributeDef().pointer();
	}
	size_t oldSize = curAdl->size();
	if (curAdl->count() != 1) {
	  Vector<CopyOwner<AttributeDefinition> > copy(oldSize);
	  for (size_t j = 0; j < oldSize; j++)
	    copy[j] = curAdl->def(j)->copy();
	  Ptr<AttributeDefinitionList> adlCopy
	    = new AttributeDefinitionList(copy,
					  defDtd().allocAttributeDefinitionListIndex(),
					  curAdl->anyCurrent(),
					  curAdl->idIndex(),
					  curAdl->notationIndex());
	  attributed[i]->setAttributeDef(adlCopy);
	  curAdl = adlCopy.pointer();
	}
	// FIXME check for multiple ID and NOTATION attributes
	for (size_t j = 0; j < adl->size(); j++) {
	  unsigned tem;
	  if (!curAdl->attributeIndex(adl->def(j)->name(), tem))
	    curAdl->append(adl->def(j)->copy());
	}
	if (!isNotation) {
	  ElementType *e = (ElementType *)attributed[i];
	  if (e->definition() != 0)
	    checkElementAttribute(e, oldSize);
	}
      }
    }
  }
  if (currentMarkup()) {
    if (isNotation) {
      Vector<ConstPtr<Notation> > v(attributed.size());
      for (size_t i = 0; i < attributed.size(); i++)
	v[i] = (Notation *)attributed[i];
      eventHandler()
	.attlistNotationDecl(new (eventAllocator())
			     AttlistNotationDeclEvent(v,
						      markupLocation(),
						      currentMarkup()));
    }
    else {
      Vector<const ElementType *> v(attributed.size());
      for (size_t i = 0; i < attributed.size(); i++)
	v[i] = (ElementType *)attributed[i];
      if (haveDefLpd())
	eventHandler()
	  .linkAttlistDecl(new (eventAllocator())
			   LinkAttlistDeclEvent(v,
						defLpdPointer(),
						markupLocation(),
						currentMarkup()));
      else
	eventHandler().attlistDecl(new (eventAllocator())
				   AttlistDeclEvent(v,
						    currentDtdPointer(),
						    markupLocation(),
						    currentMarkup()));
    }
  }
  if (isNotation) {
    Dtd::EntityIter entityIter(defDtd().generalEntityIter());
    for (;;) {
      Ptr<Entity> entity(entityIter.next());
      if (entity.isNull())
	break;
      const ExternalDataEntity *external = entity->asExternalDataEntity();
      if (external) {
	const Notation *entityNotation = external->notation();
	for (size_t  i = 0; i < attributed.size(); i++)
	  if ((Notation *)attributed[i] == entityNotation) {
	    AttributeList attributes(entityNotation->attributeDef());
	    attributes.finish(*this);
	    ((ExternalDataEntity *)entity.pointer())
	      ->setNotation((Notation *)attributed[i], attributes);
	  }
      }
    }
  }
  return 1;
}


Boolean Parser::parseAttributed(unsigned declInputLevel,
				Param &parm,
				Vector<Attributed *> &attributed,
				Boolean &isNotation)
{
  static AllowedParams
    allowNameGroupNotation(Param::name,
			   Param::nameGroup,
			   Param::indicatedReservedName + Syntax::rNOTATION);
  static AllowedParams
    allowNameGroupNotationAll(Param::name,
			      Param::nameGroup,
			      Param::indicatedReservedName
			      + Syntax::rNOTATION,
			      Param::indicatedReservedName
			      + Syntax::rALL,
			      Param::indicatedReservedName
			      + Syntax::rIMPLICIT);
  if (!parseParam(haveDefLpd()
		  ? allowNameGroupNotation
		  : allowNameGroupNotationAll,
		  declInputLevel, parm))
    return 0;
  if (parm.type == Param::indicatedReservedName + Syntax::rNOTATION) {
    if (options().warnDataAttributes)
      message(ParserMessages::dataAttributes);
    isNotation = 1;
    static AllowedParams
      allowNameGroupAll(Param::name,
			Param::nameGroup,
			Param::indicatedReservedName + Syntax::rALL,
			Param::indicatedReservedName + Syntax::rIMPLICIT);
    if (!parseParam(haveDefLpd()
		    ? allowNameNameGroup
		    : allowNameGroupAll,
		    declInputLevel, parm))
      return 0;
    if (parm.type == Param::nameGroup) {
      attributed.resize(parm.nameTokenVector.size());
      for (size_t i = 0; i < attributed.size(); i++)
	attributed[i] = lookupCreateNotation(parm.nameTokenVector[i].name);
    }
    else {
      if (parm.type != Param::name && !hadAfdrDecl() && !sd().www()) {
	message(ParserMessages::missingAfdrDecl);
	setHadAfdrDecl();
      }
      attributed.resize(1);
      attributed[0]
	= lookupCreateNotation(parm.type == Param::name
			       ? parm.token
			       : syntax().rniReservedName(Syntax::ReservedName(parm.type - Param::indicatedReservedName)));
    }
  }
  else {
    isNotation = 0;
    if (parm.type == Param::nameGroup) {
      if (options().warnAttlistGroupDecl)
	message(ParserMessages::attlistGroupDecl);
      attributed.resize(parm.nameTokenVector.size());
      for (size_t i = 0; i < attributed.size(); i++)
	attributed[i] = lookupCreateElement(parm.nameTokenVector[i].name);
    }
    else {
      if (parm.type != Param::name && !hadAfdrDecl() && !sd().www()) {
	message(ParserMessages::missingAfdrDecl);
	setHadAfdrDecl();
      }
      attributed.resize(1);
      attributed[0]
	= lookupCreateElement(parm.type == Param::name
			      ? parm.token
			      : syntax().rniReservedName(Syntax::ReservedName(parm.type - Param::indicatedReservedName)));
    }
  }
  return 1;
}

Boolean Parser::parseDeclaredValue(unsigned declInputLevel,
				   Boolean isNotation,
				   Param &parm,
				   Owner<DeclaredValue> &declaredValue)
{
  static Param::Type declaredValues[] = {
    Param::reservedName + Syntax::rCDATA,
    Param::reservedName + Syntax::rENTITY,
    Param::reservedName + Syntax::rENTITIES,
    Param::reservedName + Syntax::rID,
    Param::reservedName + Syntax::rIDREF,
    Param::reservedName + Syntax::rIDREFS,
    Param::reservedName + Syntax::rNAME,
    Param::reservedName + Syntax::rNAMES,
    Param::reservedName + Syntax::rNMTOKEN,
    Param::reservedName + Syntax::rNMTOKENS,
    Param::reservedName + Syntax::rNUMBER,
    Param::reservedName + Syntax::rNUMBERS,
    Param::reservedName + Syntax::rNUTOKEN,
    Param::reservedName + Syntax::rNUTOKENS,
    Param::reservedName + Syntax::rNOTATION,
    Param::nameTokenGroup
    };
  static AllowedParams allowDeclaredValue(declaredValues,
					  SIZEOF(declaredValues));
  if (!parseParam(allowDeclaredValue, declInputLevel, parm))
    return 0;
  enum { asDataAttribute = 01, asLinkAttribute = 02 };
  unsigned allowedFlags = asDataAttribute|asLinkAttribute;
  switch (parm.type) {
  case Param::reservedName + Syntax::rCDATA:
    declaredValue = new CdataDeclaredValue;
    break;
  case Param::reservedName + Syntax::rENTITY:
    declaredValue = new EntityDeclaredValue(0);
    allowedFlags = asLinkAttribute;
    break;
  case Param::reservedName + Syntax::rENTITIES:
    declaredValue = new EntityDeclaredValue(1);
    allowedFlags = asLinkAttribute;
    break;
  case Param::reservedName + Syntax::rID:
    declaredValue = new IdDeclaredValue;
    allowedFlags = 0;
    break;
  case Param::reservedName + Syntax::rIDREF:
    declaredValue = new IdrefDeclaredValue(0);
    allowedFlags = 0;
    break;
  case Param::reservedName + Syntax::rIDREFS:
    declaredValue = new IdrefDeclaredValue(1);
    allowedFlags = 0;
    break;
  case Param::reservedName + Syntax::rNAME:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::name, 0);
    if (options().warnNameDeclaredValue)
      message(ParserMessages::nameDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNAMES:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::name, 1);
    if (options().warnNameDeclaredValue)
      message(ParserMessages::nameDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNMTOKEN:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::nameToken, 0);
    break;
  case Param::reservedName + Syntax::rNMTOKENS:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::nameToken, 1);
    break;
  case Param::reservedName + Syntax::rNUMBER:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::number, 0);
    if (options().warnNumberDeclaredValue)
      message(ParserMessages::numberDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNUMBERS:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::number, 1);
    if (options().warnNumberDeclaredValue)
      message(ParserMessages::numberDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNUTOKEN:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::numberToken, 0);
    if (options().warnNutokenDeclaredValue)
      message(ParserMessages::nutokenDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNUTOKENS:
    declaredValue
      = new TokenizedDeclaredValue(TokenizedDeclaredValue::numberToken, 1);
    if (options().warnNutokenDeclaredValue)
      message(ParserMessages::nutokenDeclaredValue);
    break;
  case Param::reservedName + Syntax::rNOTATION:
    {
      static AllowedParams allowNameGroup(Param::nameGroup);
      if (!parseParam(allowNameGroup, declInputLevel, parm))
	return 0;
      Vector<StringC> group(parm.nameTokenVector.size());
      for (size_t i = 0; i < group.size(); i++)
	parm.nameTokenVector[i].name.swap(group[i]);
      declaredValue = new NotationDeclaredValue(group);
      allowedFlags = 0;
    }
    break;
  case Param::nameTokenGroup:
    {
      Vector<StringC> group(parm.nameTokenVector.size());
      for (size_t i = 0; i < group.size(); i++)
	parm.nameTokenVector[i].name.swap(group[i]);
      declaredValue = new NameTokenGroupDeclaredValue(group);
    }
    break;
  default:
    CANNOT_HAPPEN();
  }
  if (isNotation) {
    if (!(allowedFlags & asDataAttribute))
      message(ParserMessages::dataAttributeDeclaredValue);
  }
  else if (haveDefLpd() && !isNotation && !(allowedFlags & asLinkAttribute))
    message(ParserMessages::linkAttributeDeclaredValue);
  return 1;
}

Boolean Parser::parseDefaultValue(unsigned declInputLevel,
				  Boolean isNotation,
				  Param &parm,
				  const StringC &attributeName,
				  Owner<DeclaredValue> &declaredValue,
				  Owner<AttributeDefinition> &def,
				  Boolean &anyCurrent)
{
  // default value
  static AllowedParams
    allowDefaultValue(Param::indicatedReservedName + Syntax::rFIXED,
		      Param::indicatedReservedName + Syntax::rREQUIRED,
		      Param::indicatedReservedName + Syntax::rCURRENT,
		      Param::indicatedReservedName + Syntax::rCONREF,
		      Param::indicatedReservedName + Syntax::rIMPLIED,
		      Param::attributeValue,
		      Param::attributeValueLiteral);
  static AllowedParams
    allowTokenDefaultValue(Param::indicatedReservedName + Syntax::rFIXED,
		      Param::indicatedReservedName + Syntax::rREQUIRED,
		      Param::indicatedReservedName + Syntax::rCURRENT,
		      Param::indicatedReservedName + Syntax::rCONREF,
		      Param::indicatedReservedName + Syntax::rIMPLIED,
		      Param::attributeValue,
		      Param::tokenizedAttributeValueLiteral);
  if (!parseParam(declaredValue->tokenized()
		  ? allowTokenDefaultValue
		  : allowDefaultValue, declInputLevel, parm))
    return 0;
  switch (parm.type) {
  case Param::indicatedReservedName + Syntax::rFIXED:
    {
      static AllowedParams allowValue(Param::attributeValue,
				      Param::attributeValueLiteral);
      static AllowedParams
	allowTokenValue(Param::attributeValue,
			Param::tokenizedAttributeValueLiteral);
      if (!parseParam(declaredValue->tokenized()
		      ? allowTokenValue
		      : allowValue, declInputLevel, parm))
	return 0;
      unsigned specLength = 0;
      AttributeValue *value = declaredValue->makeValue(parm.literalText,
						       *this,
						       attributeName,
						       specLength);
      if (declaredValue->isId())
	message(ParserMessages::idDeclaredValue);
      def = new FixedAttributeDefinition(attributeName,
					 declaredValue.extract(),
					 value);
    }
    break;
  case Param::attributeValue:
    if (options().warnAttributeValueNotLiteral)
      message(ParserMessages::attributeValueNotLiteral);
    // falll through
  case Param::attributeValueLiteral:
  case Param::tokenizedAttributeValueLiteral:
    {
      unsigned specLength = 0;
      AttributeValue *value = declaredValue->makeValue(parm.literalText,
						       *this,
						       attributeName,
						       specLength);
      if (declaredValue->isId())
	message(ParserMessages::idDeclaredValue);
      def = new DefaultAttributeDefinition(attributeName,
					   declaredValue.extract(),
					   value);
    }
    break;
  case Param::indicatedReservedName + Syntax::rREQUIRED:
    def = new RequiredAttributeDefinition(attributeName,
					  declaredValue.extract());
    break;
  case Param::indicatedReservedName + Syntax::rCURRENT:
    anyCurrent = 1;	       
    if (declaredValue->isId())
      message(ParserMessages::idDeclaredValue);
    def = new CurrentAttributeDefinition(attributeName,
					 declaredValue.extract(),
					 defDtd().allocCurrentAttributeIndex());
    if (isNotation)
      message(ParserMessages::dataAttributeDefaultValue);
    else if (haveDefLpd())
      message(ParserMessages::linkAttributeDefaultValue);
    else if (options().warnCurrent)
      message(ParserMessages::currentAttribute);
    break;
  case Param::indicatedReservedName + Syntax::rCONREF:
    if (declaredValue->isId())
      message(ParserMessages::idDeclaredValue);
    def = new ConrefAttributeDefinition(attributeName,
					declaredValue.extract());
    if (isNotation)
      message(ParserMessages::dataAttributeDefaultValue);
    else if (haveDefLpd())
      message(ParserMessages::linkAttributeDefaultValue);
    else if (options().warnConref)
      message(ParserMessages::conrefAttribute);
    break;
  case Param::indicatedReservedName + Syntax::rIMPLIED:
    def = new ImpliedAttributeDefinition(attributeName,
					 declaredValue.extract());
    break;
  default:
    CANNOT_HAPPEN();
  }
  return 1;
}

// parm contains either system or public

Boolean Parser::parseExternalId(const AllowedParams &sysidAllow,
				const AllowedParams &endAllow,
				Boolean maybeWarnMissingSystemId,
				unsigned declInputLevel,
				Param &parm,
				ExternalId &id)
{
  id.setLocation(currentLocation());
  if (parm.type == Param::reservedName + Syntax::rPUBLIC) {
    static AllowedParams allowMinimumLiteral(Param::minimumLiteral);
    if (!parseParam(allowMinimumLiteral, declInputLevel, parm))
      return 0;
    const MessageType1 *err;
    if (id.setPublic(parm.literalText, sd().internalCharset(), syntax().space(),
		      err)) {
      PublicId::TextClass textClass;
      if (sd().formal() && id.publicId()->getTextClass(textClass) && textClass == PublicId::SD)
	message(ParserMessages::wwwRequired);
    }
    else if (sd().formal())
      message(*err,
	      StringMessageArg(*id.publicIdString()));
  }
  if (!parseParam(sysidAllow, declInputLevel, parm))
    return 0;
  if (parm.type == Param::systemIdentifier) {
    id.setSystem(parm.literalText);
    if (!parseParam(endAllow, declInputLevel, parm))
      return 0;
  }
  else if (options().warnMissingSystemId && maybeWarnMissingSystemId)
    message(ParserMessages::missingSystemId);
  return 1;
}

Boolean Parser::parseNotationDecl()
{
  unsigned declInputLevel = inputLevel();
  Param parm;
  if (!parseParam(allowName, declInputLevel, parm))
    return 0;
  Notation *nt = lookupCreateNotation(parm.token);
  if (validate() && nt->defined())
    message(ParserMessages::duplicateNotationDeclaration,
	    StringMessageArg(parm.token));
  static AllowedParams
    allowPublicSystem(Param::reservedName + Syntax::rPUBLIC,
		      Param::reservedName + Syntax::rSYSTEM);
  if (!parseParam(allowPublicSystem, declInputLevel, parm))
    return 0;


  static AllowedParams allowSystemIdentifierMdc(Param::systemIdentifier,
						Param::mdc);

  ExternalId id;
  if (!parseExternalId(allowSystemIdentifierMdc, allowMdc,
		       parm.type == Param::reservedName + Syntax::rSYSTEM,
		       declInputLevel, parm, id))
    return 0;
  if (validate() && sd().formal()) {
    PublicId::TextClass textClass;
    const PublicId *publicId = id.publicId();
    if (publicId
	&& publicId->getTextClass(textClass)
	&& textClass != PublicId::NOTATION)
      message(ParserMessages::notationIdentifierTextClass);
  }
  if (!nt->defined()) {
    nt->setExternalId(id, markupLocation());
    nt->generateSystemId(*this);
    if (currentMarkup())
      eventHandler().notationDecl(new (eventAllocator())
				  NotationDeclEvent(nt, markupLocation(),
						    currentMarkup()));
  }
  return 1;
}

Boolean Parser::parseEntityDecl()
{
  unsigned declInputLevel = inputLevel();
  Param parm;

  static AllowedParams
    allowEntityNamePero(Param::entityName,
			Param::indicatedReservedName + Syntax::rDEFAULT,
			Param::pero);

  if (!parseParam(allowEntityNamePero, declInputLevel, parm))
    return 0;

  Entity::DeclType declType;
  StringC name;			// empty for default entity
  if (parm.type == Param::pero) {
    declType = Entity::parameterEntity;
    static AllowedParams allowParamEntityName(Param::paramEntityName);
    if (!parseParam(allowParamEntityName, declInputLevel, parm))
      return 0;
    parm.token.swap(name);
  }
  else {
    declType = Entity::generalEntity;
    if (parm.type == Param::entityName)
      parm.token.swap(name);
    else if (options().warnDefaultEntityDecl)
      message(ParserMessages::defaultEntityDecl);
  }
  static AllowedParams
    allowEntityTextType(Param::paramLiteral,
			Param::reservedName + Syntax::rCDATA,
			Param::reservedName + Syntax::rSDATA,
			Param::reservedName + Syntax::rPI,
			Param::reservedName + Syntax::rSTARTTAG,
			Param::reservedName + Syntax::rENDTAG,
			Param::reservedName + Syntax::rMS,
			Param::reservedName + Syntax::rMD,
			Param::reservedName + Syntax::rSYSTEM,
			Param::reservedName + Syntax::rPUBLIC);

  if (!parseParam(allowEntityTextType, declInputLevel, parm))
    return 0;
  Location typeLocation(currentLocation());
  Entity::DataType dataType = Entity::sgmlText;
  InternalTextEntity::Bracketed bracketed = InternalTextEntity::none;
  switch (parm.type) {
  case Param::reservedName + Syntax::rSYSTEM:
  case Param::reservedName + Syntax::rPUBLIC:
    return parseExternalEntity(name, declType, declInputLevel, parm);
  case Param::reservedName + Syntax::rCDATA:
    dataType = Entity::cdata;
    if (options().warnInternalCdataEntity)
      message(ParserMessages::internalCdataEntity);
    break;
  case Param::reservedName + Syntax::rSDATA:
    dataType = Entity::sdata;
    if (options().warnInternalSdataEntity)
      message(ParserMessages::internalSdataEntity);
    break;
  case Param::reservedName + Syntax::rPI:
    dataType = Entity::pi;
    if (options().warnPiEntity)
      message(ParserMessages::piEntity);
    break;
  case Param::reservedName + Syntax::rSTARTTAG:
    bracketed = InternalTextEntity::starttag;
    if (options().warnBracketEntity)
      message(ParserMessages::bracketEntity);
    break;
  case Param::reservedName + Syntax::rENDTAG:
    bracketed = InternalTextEntity::endtag;
    if (options().warnBracketEntity)
      message(ParserMessages::bracketEntity);
    break;
  case Param::reservedName + Syntax::rMS:
    bracketed = InternalTextEntity::ms;
    if (options().warnBracketEntity)
      message(ParserMessages::bracketEntity);
    break;
  case Param::reservedName + Syntax::rMD:
    bracketed = InternalTextEntity::md;
    if (options().warnBracketEntity)
      message(ParserMessages::bracketEntity);
    break;
  }
  if (parm.type != Param::paramLiteral) {
    if (!parseParam(allowParamLiteral, declInputLevel, parm))
      return 0;
  }
  Text text;
  parm.literalText.swap(text);
  if (bracketed != InternalTextEntity::none) {
    StringC open;
    StringC close;
    switch (bracketed) {
    case InternalTextEntity::starttag:
      open = syntax().delimGeneral(Syntax::dSTAGO);
      close = syntax().delimGeneral(Syntax::dTAGC);
      break;
    case InternalTextEntity::endtag:
      open = syntax().delimGeneral(Syntax::dETAGO);
      close = syntax().delimGeneral(Syntax::dTAGC);
      break;
    case InternalTextEntity::ms:
      open = syntax().delimGeneral(Syntax::dMDO);
      open += syntax().delimGeneral(Syntax::dDSO);
      close = syntax().delimGeneral(Syntax::dMSC);
      close += syntax().delimGeneral(Syntax::dMDC);
      break;
    case InternalTextEntity::md:
      open = syntax().delimGeneral(Syntax::dMDO);
      close = syntax().delimGeneral(Syntax::dMDC);
      break;
    default:
      CANNOT_HAPPEN();
    }
    text.insertChars(open, Location(new BracketOrigin(typeLocation,
						      BracketOrigin::open),
				    0));
    text.addChars(close, Location(new BracketOrigin(typeLocation,
						    BracketOrigin::close),
				  0));
    if (text.size() > syntax().litlen()
	&& text.size() - open.size() - close.size() <= syntax().litlen())
      message(ParserMessages::bracketedLitlen,
	      NumberMessageArg(syntax().litlen()));
  }
  if (!parseParam(allowMdc, declInputLevel, parm))
    return 0;
  if (declType == Entity::parameterEntity
      && (dataType == Entity::cdata || dataType == Entity::sdata)) {
    message(ParserMessages::internalParameterDataEntity,
	    StringMessageArg(name));
    return 1;
  }
  Ptr<Entity> entity;
  switch (dataType) {
  case Entity::cdata:
    entity = new InternalCdataEntity(name, markupLocation(), text);
    break;
  case Entity::sdata:
    entity = new InternalSdataEntity(name, markupLocation(), text);
    break;
  case Entity::pi:
    entity = new PiEntity(name, declType, markupLocation(), text);
    break;
  case Entity::sgmlText:
    entity = new InternalTextEntity(name, declType, markupLocation(), text, bracketed);
    break;
  default:
    CANNOT_HAPPEN();
    break;
  }
  maybeDefineEntity(entity);
  return 1;
}

Boolean Parser::parseExternalEntity(StringC &name,
				    Entity::DeclType declType,
				    unsigned declInputLevel,
				    Param &parm)
{
  static AllowedParams
    allowSystemIdentifierEntityTypeMdc(Param::systemIdentifier,
				       Param::reservedName + Syntax::rSUBDOC,
				       Param::reservedName + Syntax::rCDATA,
				       Param::reservedName + Syntax::rSDATA,
				       Param::reservedName + Syntax::rNDATA,
				       Param::mdc);
  static AllowedParams
    allowEntityTypeMdc(Param::reservedName + Syntax::rSUBDOC,
		       Param::reservedName + Syntax::rCDATA,
		       Param::reservedName + Syntax::rSDATA,
		       Param::reservedName + Syntax::rNDATA,
		       Param::mdc);
  
  ExternalId id;
  if (!parseExternalId(allowSystemIdentifierEntityTypeMdc, allowEntityTypeMdc,
		       1, declInputLevel, parm, id))
    return 0;
  if (parm.type == Param::mdc) {
    maybeDefineEntity(new ExternalTextEntity(name, declType, markupLocation(),
					     id));
    return 1;
  }
  Ptr<Entity> entity;
  if (parm.type == Param::reservedName + Syntax::rSUBDOC) {
    if (sd().subdoc() == 0)
      message(ParserMessages::subdocEntity, StringMessageArg(name));
    if (!parseParam(allowMdc, declInputLevel, parm))
      return 0;
    entity = new SubdocEntity(name, markupLocation(), id);
  }
  else {
    Entity::DataType dataType;
    switch (parm.type) {
    case Param::reservedName + Syntax::rCDATA:
      dataType = Entity::cdata;
      if (options().warnExternalCdataEntity)
	message(ParserMessages::externalCdataEntity);
      break;
    case Param::reservedName + Syntax::rSDATA:
      dataType = Entity::sdata;
      if (options().warnExternalSdataEntity)
	message(ParserMessages::externalSdataEntity);
      break;
    case Param::reservedName + Syntax::rNDATA:
      dataType = Entity::ndata;
      break;
    default:
      CANNOT_HAPPEN();
    }
    if (!parseParam(allowName, declInputLevel, parm))
      return 0;
    Ptr<Notation> notation(lookupCreateNotation(parm.token));
    if (!parseParam(allowDsoMdc, declInputLevel, parm))
      return 0;
    AttributeList attributes(notation->attributeDef());
    if (parm.type == Param::dso) {
      if (attributes.size() == 0)
	message(ParserMessages::notationNoAttributes,
		StringMessageArg(notation->name()));
      Boolean netEnabling;
      Ptr<AttributeDefinitionList> newAttDef;
      if (!parseAttributeSpec(1, attributes, netEnabling, newAttDef))
	return 0;
      if (!newAttDef.isNull()) {
	newAttDef->setIndex(defDtd().allocAttributeDefinitionListIndex());
	notation->setAttributeDef(newAttDef);
      }
      if (attributes.nSpec() == 0)
	message(ParserMessages::emptyDataAttributeSpec);
      if (!parseParam(allowMdc, declInputLevel, parm))
	return 0;
    }
    else
      attributes.finish(*this);
    entity = new ExternalDataEntity(name, dataType, markupLocation(), id, notation,
				    attributes);
  }
  if (declType == Entity::parameterEntity) {
    message(ParserMessages::externalParameterDataSubdocEntity,
	    StringMessageArg(name));
    return 1;
  }
  maybeDefineEntity(entity);
  return 1;
}

Notation *Parser::lookupCreateNotation(const StringC &name)
{
  Ptr<Notation> nt = defDtd().lookupNotation(name);
  if (nt.isNull()) {
    nt = new Notation(name, defDtd().namePointer(), defDtd().isBase());
    defDtd().insertNotation(nt);
  }
  return nt.pointer();
}

void Parser::maybeDefineEntity(const Ptr<Entity> &entity)
{
  Dtd &dtd = defDtd();
  if (haveDefLpd())
    entity->setDeclIn(dtd.namePointer(),
		      dtd.isBase(),
		      defLpd().namePointer(),
		      defLpd().active());
  else
    entity->setDeclIn(dtd.namePointer(), dtd.isBase());
  Boolean ignored = 0;
  if (entity->name().size() == 0) {
    const Entity *oldEntity = dtd.defaultEntity().pointer();
    if (oldEntity == 0
	|| (!oldEntity->declInActiveLpd() && entity->declInActiveLpd()))
      dtd.setDefaultEntity(entity, *this);
    else {
      ignored = 1;
      if (options().warnDuplicateEntity)
	message(ParserMessages::duplicateEntityDeclaration,
		StringMessageArg(syntax().rniReservedName(Syntax::rDEFAULT)));
    }
  }
  else {
    Ptr<Entity> oldEntity = dtd.insertEntity(entity);
    if (oldEntity.isNull())
      entity->generateSystemId(*this);
    else if (oldEntity->defaulted()) {
      dtd.insertEntity(entity, 1);
      message(ParserMessages::defaultedEntityDefined,
	      StringMessageArg(entity->name()));
      entity->generateSystemId(*this);
    }
    else {
      if (entity->declInActiveLpd() && !oldEntity->declInActiveLpd()) {
	dtd.insertEntity(entity, 1);
	entity->generateSystemId(*this);
      }
      else {
	ignored = 1;
	if (options().warnDuplicateEntity)
	  message(entity->declType() == Entity::parameterEntity
		  ? ParserMessages::duplicateParameterEntityDeclaration
		  : ParserMessages::duplicateEntityDeclaration,
		  StringMessageArg(entity->name()));
      }
    }
  }
  if (currentMarkup())
    eventHandler().entityDecl(new (eventAllocator())
			      EntityDeclEvent(entity, ignored,
					      markupLocation(),
					      currentMarkup()));
}

Boolean Parser::parseShortrefDecl()
{
  if (!defDtd().isBase())
    message(ParserMessages::shortrefOnlyInBaseDtd);

  unsigned declInputLevel = inputLevel();
  Param parm;

  if (!parseParam(allowName, declInputLevel, parm))
    return 0;
  ShortReferenceMap *map = lookupCreateMap(parm.token);
  int valid = 1;
  if (map->defined()) {
    message(ParserMessages::duplicateShortrefDeclaration,
	    StringMessageArg(parm.token),
	    map->defLocation());
    valid = 0;
  }
  else
    map->setDefLocation(markupLocation());
  if (!parseParam(allowParamLiteral, declInputLevel, parm))
    return 0;
  Vector<StringC> vec;
  do {
    StringC delim(parm.literalText.string());
    const SubstTable<Char> *table = instanceSyntax().generalSubstTable();
    for (size_t i = 0; i < delim.size(); i++)
      table->subst(delim[i]);
    size_t srIndex;
    if (!defDtd().shortrefIndex(delim, instanceSyntax(), srIndex)) {
      message(ParserMessages::unknownShortrefDelim,
	      StringMessageArg(prettifyDelim(delim)));
      valid = 0;
    }
    static AllowedParams allowEntityName(Param::entityName);
    if (!parseParam(allowEntityName, declInputLevel, parm))
      return 0;
    if (valid) {
      if (srIndex >= vec.size())
	vec.resize(srIndex + 1);
      if (vec[srIndex].size() > 0) {
	message(ParserMessages::delimDuplicateMap,
		StringMessageArg(prettifyDelim(delim)));
	valid = 0;
      }
      else
	parm.token.swap(vec[srIndex]);
    }
    static AllowedParams allowParamLiteralMdc(Param::paramLiteral, Param::mdc);
    if (!parseParam(allowParamLiteralMdc, declInputLevel, parm))
      return 0;
  } while (parm.type != Param::mdc);
  if (valid) {
    map->setNameMap(vec);
    if (currentMarkup())
      eventHandler().shortrefDecl(new (eventAllocator())
				  ShortrefDeclEvent(map,
						    currentDtdPointer(),
						    markupLocation(),
						    currentMarkup()));
  }
  return 1;
}

StringC Parser::prettifyDelim(const StringC &delim)
{
  StringC prettyDelim;
  for (size_t i = 0; i < delim.size(); i++) {
    const StringC *nameP;
    if (syntax().charFunctionName(delim[i], nameP)) {
      prettyDelim += syntax().delimGeneral(Syntax::dCRO);
      prettyDelim += *nameP;
      prettyDelim += syntax().delimGeneral(Syntax::dREFC);
    }
    else
      prettyDelim += delim[i];
  }
  return prettyDelim;
}

ShortReferenceMap *Parser::lookupCreateMap(const StringC &name)
{
  ShortReferenceMap *map = defDtd().lookupShortReferenceMap(name);
  if (!map) {
    map = new ShortReferenceMap(name);
    defDtd().insertShortReferenceMap(map);
  }
  return map;
}

Boolean Parser::parseUsemapDecl()
{
  if (!inInstance() && !defDtd().isBase())
    message(ParserMessages::usemapOnlyInBaseDtd);

  unsigned declInputLevel = inputLevel();
  Param parm;
  static AllowedParams
    allowNameEmpty(Param::name,
		   Param::indicatedReservedName + Syntax::rEMPTY);
  if (!parseParam(allowNameEmpty, declInputLevel, parm))
    return 0;
  const ShortReferenceMap *map;
  if (parm.type == Param::name) {
    if (inInstance()) {
      map = currentDtd().lookupShortReferenceMap(parm.token);
      if (!map)
	message(ParserMessages::undefinedShortrefMapInstance,
		StringMessageArg(parm.token));
    }
    else {
      ShortReferenceMap *tem = lookupCreateMap(parm.token);
      tem->setUsed();
      map = tem;
    }
  }
  else
    map = &theEmptyMap;
  static AllowedParams
    allowNameNameGroupMdc(Param::name, Param::nameGroup, Param::mdc);
  if (!parseParam(allowNameNameGroupMdc, declInputLevel, parm))
    return 0;
  if (parm.type != Param::mdc) {
    if (inInstance()) {
      message(ParserMessages::usemapAssociatedElementTypeInstance);
      if (!parseParam(allowMdc, declInputLevel, parm))
	return 0;
    }
    else {
      Vector<const ElementType *> v;
      if (parm.type == Param::name) {
	ElementType *e = lookupCreateElement(parm.token);
	v.push_back(e);
	if (!e->map())
	  e->setMap(map);
      }
      else {
	v.resize(parm.nameTokenVector.size());
	for (size_t i = 0; i < parm.nameTokenVector.size(); i++) {
	  ElementType *e
	    = lookupCreateElement(parm.nameTokenVector[i].name);
	  v[i] = e;
	  if (!e->map())
	    e->setMap(map);
	}
      }
      if (!parseParam(allowMdc, declInputLevel, parm))
	return 0;
      if (currentMarkup())
	eventHandler().usemap(new (eventAllocator())
			      UsemapEvent(map, v,
					  currentDtdPointer(),
					  markupLocation(),
					  currentMarkup()));
    }
  }
  else {
    if (!inInstance())
      message(ParserMessages::usemapAssociatedElementTypeDtd);
    else if (map) {
      if (map != &theEmptyMap && !map->defined())
	message(ParserMessages::undefinedShortrefMapInstance,
		StringMessageArg(map->name()));
      else {
	if (currentMarkup()) {
	  Vector<const ElementType *> v;
	  eventHandler().usemap(new (eventAllocator())
				UsemapEvent(map, v,
					    currentDtdPointer(),
					    markupLocation(),
					    currentMarkup()));
	}
	currentElement().setMap(map);
      }
    }
  }
  return 1;
}

Boolean Parser::parseDoctypeDeclStart()
{
  if (hadDtd() && !sd().concur() && !sd().explicitLink())
    message(ParserMessages::multipleDtds);
  if (hadLpd())
    message(ParserMessages::dtdAfterLpd);
  unsigned declInputLevel = inputLevel();
  Param parm;
  
  if (!parseParam(allowName, declInputLevel, parm))
    return 0;
  StringC name;
  parm.token.swap(name);
  if (!lookupDtd(name).isNull())
    message(ParserMessages::duplicateDtd, StringMessageArg(name));
  static AllowedParams
    allowPublicSystemDsoMdc(Param::reservedName + Syntax::rPUBLIC,
			    Param::reservedName + Syntax::rSYSTEM,
			    Param::dso,
			    Param::mdc);
  if (!parseParam(allowPublicSystemDsoMdc, declInputLevel, parm))
    return 0;
  ConstPtr<Entity> entity;
  if (parm.type == Param::reservedName + Syntax::rPUBLIC
      || parm.type == Param::reservedName + Syntax::rSYSTEM) {
    static AllowedParams allowSystemIdentifierDsoMdc(Param::systemIdentifier,
						     Param::dso, Param::mdc);
    ExternalId id;
    if (!parseExternalId(allowSystemIdentifierDsoMdc, allowDsoMdc,
			 1, declInputLevel, parm, id))
      return 0;
    Ptr<Entity> tem
      = new ExternalTextEntity(name, Entity::doctype, markupLocation(), id);
    tem->generateSystemId(*this);
    entity = tem;
#if 0
    eventHandler()
      .externalEntityDecl(new (eventAllocator())
			  ExternalEntityDeclEvent(entity, 0));
#endif
  }
  else if (parm.type == Param::mdc) {
    if (!sd().implydefElement()) {
      message(ParserMessages::noDtdSubset);
      enableImplydef();
    }
  }
  // Discard mdc or dso
  if (currentMarkup())
    currentMarkup()->resize(currentMarkup()->size() - 1);
  eventHandler().startDtd(new (eventAllocator())
			  StartDtdEvent(name, entity, parm.type == Param::dso,
					markupLocation(),
					currentMarkup()));
  startDtd(name);
  if (parm.type == Param::mdc) {
    // unget the mdc
    currentInput()->ungetToken();
    if (entity.isNull()) {
      (void)parseDoctypeDeclEnd();
      return 1;
    }
    // reference the entity
    Ptr<EntityOrigin> origin
      = EntityOrigin::make(internalAllocator(), entity, currentLocation());
    entity->dsReference(*this, origin);
    if (inputLevel() == 1) {	// reference failed
      (void)parseDoctypeDeclEnd();
      return 1;
    }
  }
  else if (!entity.isNull())
    setDsEntity(entity);
  setPhase(declSubsetPhase);
  return 1;
}

void Parser::implyDtd(const StringC &gi)
{
  startMarkup(eventsWanted().wantPrologMarkup(), Location());
#if 0
  if (currentMarkup()) {
    currentMarkup()->addDelim(Syntax::dMDO);
    currentMarkup()->addReservedName(Syntax::rDOCTYPE,
				     syntax().reservedName(Syntax::rDOCTYPE));
    currentMarkup()->addS(syntax().space());
    currentMarkup()->addName(gi.data(), gi.size());
  }
#endif
  if (sd().implydefElement() && !sd().implydefDoctype()) {
    eventHandler().startDtd(new (eventAllocator())
				  StartDtdEvent(gi, ConstPtr<Entity>(), 0,
					markupLocation(),
					currentMarkup()));
    startDtd(gi);
    parseDoctypeDeclEnd(1);
    return;
  }
  ExternalId id;
  // The null location indicates that this is a fake entity.
  Entity *tem = new ExternalTextEntity(gi, Entity::doctype, Location(), id);
  ConstPtr<Entity> entity(tem);
  if (sd().implydefDoctype())
    tem->generateSystemId(*this);
  else {
    // Don't use Entity::generateSystemId because we don't want an error
    // if it fails.
    StringC str;
    if (!entityCatalog().lookup(*entity, syntax(), sd().internalCharset(),
			        messenger(), str)) {
      message(ParserMessages::noDtd);
      enableImplydef();
      eventHandler().startDtd(new (eventAllocator())
				   StartDtdEvent(gi, ConstPtr<Entity>(), 0,
				                 markupLocation(),
					         currentMarkup()));
      startDtd(gi);
      parseDoctypeDeclEnd(1);
      return;
    }
    id.setEffectiveSystem(str);
#if 0
    if (currentMarkup()) {
      currentMarkup()->addS(syntax().space());
      currentMarkup()->addReservedName(Syntax::rSYSTEM,
				       syntax().reservedName(Syntax::rSYSTEM));
    }
#endif
    entity = new ExternalTextEntity(gi,
				    Entity::doctype,
				    Location(),
				    id);
    StringC declStr;
    declStr += syntax().delimGeneral(Syntax::dMDO);
    declStr += syntax().reservedName(Syntax::rDOCTYPE);
    declStr += syntax().space();
    declStr += gi;
    declStr += syntax().space();
    declStr += syntax().reservedName(Syntax::rSYSTEM);
    declStr += syntax().delimGeneral(Syntax::dMDC);
    message(ParserMessages::implyingDtd, StringMessageArg(declStr));
  }
  Ptr<EntityOrigin> origin
    = EntityOrigin::make(internalAllocator(), entity, currentLocation());
  eventHandler().startDtd(new (eventAllocator())
			  StartDtdEvent(gi, entity, 0,
					markupLocation(),
					currentMarkup()));
  startDtd(gi);
  entity->dsReference(*this, origin);
  if (inputLevel() == 1)
    parseDoctypeDeclEnd(1);
  else
    setPhase(declSubsetPhase);
}

Boolean Parser::parseDoctypeDeclEnd(Boolean fake)
{
  checkDtd(defDtd());
  Ptr<Dtd> tem(defDtdPointer());
  endDtd();
  if (fake) {
    startMarkup(eventsWanted().wantPrologMarkup(), Location());
#if 0
    if (currentMarkup())
      currentMarkup()->addDelim(Syntax::dMDC);
#endif
  }
  else {
    startMarkup(eventsWanted().wantPrologMarkup(), currentLocation());
    Param parm;
    // End DTD before parsing final param so parameter entity reference
    // not allowed between ] and >.
    if (!parseParam(allowMdc, inputLevel(), parm))
      return 0;
  }
  eventHandler().endDtd(new (eventAllocator()) EndDtdEvent(tem,
							   markupLocation(),
							   currentMarkup()));
#if 0
  if (fake) {
    Char c = syntax().standardFunction(Syntax::fRE);
    eventHandler().sSep(new (eventAllocator())
			SSepEvent(&c, 1, Location(), 1));
  }
#endif
  return 1;
}

void Parser::checkDtd(Dtd &dtd)
{
  if (dtd.isBase())
    addNeededShortrefs(dtd, instanceSyntax());
  if (sd().www() || !options().errorAfdr)
    addCommonAttributes(dtd);
  Dtd::ElementTypeIter elementIter(dtd.elementTypeIter());
  ElementType *p;
  ConstPtr<ElementDefinition> def;
  int i = 0;
  while ((p = elementIter.next()) != 0) {
    if (p->definition() == 0) {
      if (p->name() == dtd.name()) {
	if (validate() && !implydefElement())
	  message(ParserMessages::documentElementUndefined);
      }
      else if (options().warnUndefinedElement)
	message(ParserMessages::dtdUndefinedElement, StringMessageArg(p->name()));
      if (def.isNull())
	def = new ElementDefinition(currentLocation(),
				    size_t(ElementDefinition::undefinedIndex),
				    0,
				    ElementDefinition::any);
      p->setElementDefinition(def, i++);
    }
    const ShortReferenceMap *map = p->map();
    if (map != 0 && map != &theEmptyMap && !map->defined()) {
      if (validate())
	message(ParserMessages::undefinedShortrefMapDtd,
	        StringMessageArg(map->name()),
	        StringMessageArg(p->name()));
      p->setMap(0);
    }
  }
  Dtd::ShortReferenceMapIter mapIter(dtd.shortReferenceMapIter());
  int nShortref = dtd.nShortref();
  for (;;) {
    ShortReferenceMap *map = mapIter.next();
    if (!map)
      break;
    Vector<ConstPtr<Entity> > entityMap(nShortref);
    for (i = 0; i < nShortref; i++) {
      const StringC *entityName = map->entityName(i);
      if (entityName) {
	ConstPtr<Entity> entity
	  = lookupEntity(0, *entityName, map->defLocation(), 0);
	if (entity.isNull()) {
	  setNextLocation(map->defLocation());
	  message(ParserMessages::mapEntityUndefined,
		  StringMessageArg(*entityName),
		  StringMessageArg(map->name()));
	}
	else {
	  if (entity->defaulted() && options().warnDefaultEntityReference) {
	    setNextLocation(map->defLocation());
	    message(ParserMessages::mapDefaultEntity,
		    StringMessageArg(*entityName),
		    StringMessageArg(map->name()));
	  }
	  entityMap[i] = entity;
	}
      }
    }
    map->setEntityMap(entityMap);
    if (options().warnUnusedMap && !map->used()) {
      setNextLocation(map->defLocation());
      message(ParserMessages::unusedMap, StringMessageArg(map->name()));
    }
  }
  if (options().warnUnusedParam) {
    Dtd::ConstEntityIter entityIter(((const Dtd &)dtd).parameterEntityIter());
    for (;;) {
      ConstPtr<Entity> entity(entityIter.next());
      if (entity.isNull())
	break;
      if (!entity->used() && !maybeStatusKeyword(*entity)) {
	setNextLocation(entity->defLocation());
	message(ParserMessages::unusedParamEntity,
		StringMessageArg(entity->name()));
      }
    }
  }
  if (!validate())
    return;
  Dtd::ConstEntityIter entityIter(((const Dtd &)dtd).generalEntityIter());
  for (;;) {
    ConstPtr<Entity> entity(entityIter.next());
    if (entity.isNull())
      break;
    const ExternalDataEntity *external = entity->asExternalDataEntity();
    if (external) {
      const Notation *notation = external->notation();
      if (!notation->defined()) {
	setNextLocation(external->defLocation());
	message(ParserMessages::entityNotationUndefined,
		StringMessageArg(notation->name()),
		StringMessageArg(external->name()));
      }
    }
  }
  Dtd::NotationIter notationIter(dtd.notationIter());
  for (;;) {
    ConstPtr<Notation> notation(notationIter.next());
    if (notation.isNull())
      break;
    if (!notation->defined() && !notation->attributeDef().isNull())
      message(ParserMessages::attlistNotationUndefined,
	      StringMessageArg(notation->name()));
  }
}

void Parser::addCommonAttributes(Dtd &dtd)
{
  Ptr<AttributeDefinitionList> commonAdl[2];
  {
    ElementType *e = dtd.removeElementType(syntax()
					   .rniReservedName(Syntax::rALL));
    if (e) {
      commonAdl[0] = e->attributeDef();
      delete e;
      lookupCreateElement(syntax().rniReservedName(Syntax::rIMPLICIT));
    }
  }
  {
    Ptr<Notation> allNotation
      = dtd.removeNotation(syntax().rniReservedName(Syntax::rALL));
    if (!allNotation.isNull()) {
      commonAdl[1] = allNotation->attributeDef();
      lookupCreateNotation(syntax().rniReservedName(Syntax::rIMPLICIT));
    }
  }
  Dtd::ElementTypeIter elementIter(dtd.elementTypeIter());
  Dtd::NotationIter notationIter(dtd.notationIter());
  Vector<PackedBoolean> doneAdl(dtd.nAttributeDefinitionList(),
				PackedBoolean(0));
  for (int isNotation = 0; isNotation < 2; isNotation++) {
    if (!commonAdl[isNotation].isNull()) {
      doneAdl[commonAdl[isNotation]->index()] = 1;
      for (;;) {
	Attributed *a;
	if (!isNotation)
	  a = elementIter.next();
	else
	  a = notationIter.next().pointer();
	if (!a)
	  break;
	Ptr<AttributeDefinitionList> adl = a->attributeDef();
	if (adl.isNull())
	  a->setAttributeDef(commonAdl[isNotation]);
	else if (!doneAdl[adl->index()]) {
	  doneAdl[adl->index()] = 1;
	  for (size_t j = 0; j < commonAdl[isNotation]->size(); j++) {
	    unsigned tem;
	    if (!adl->attributeIndex(commonAdl[isNotation]->def(j)->name(),
				     tem))
	      adl->append(commonAdl[isNotation]->def(j)->copy());
	  }
	}
      }
    }
  }
  {
    ElementType *e = dtd.removeElementType(syntax()
					   .rniReservedName(Syntax::rIMPLICIT));
    if (e)
      dtd.setImplicitElementAttributeDef(e->attributeDef());
    delete e;
  }
  {
    Ptr<Notation> n
      = dtd.removeNotation(syntax().rniReservedName(Syntax::rIMPLICIT));
    if (!n.isNull())
      dtd.setImplicitNotationAttributeDef(n->attributeDef());
  }
}

Boolean Parser::maybeStatusKeyword(const Entity &entity)
{
  const InternalEntity *internal = entity.asInternalEntity();
  if (!internal)
    return 0;
  const StringC &text = internal->string();
  static const Syntax::ReservedName statusKeywords[] = {
    Syntax::rINCLUDE, Syntax::rIGNORE
    };
  for (size_t i = 0; i < SIZEOF(statusKeywords); i++) {
    const StringC &keyword = instanceSyntax().reservedName(statusKeywords[i]);
    size_t j = 0;
    while (j < text.size() && instanceSyntax().isS(text[j]))
      j++;
    size_t k = 0;
    while (j < text.size()
	   && k < keyword.size()
	   && ((*instanceSyntax().generalSubstTable())[text[j]]
	       == keyword[k]))
      j++, k++;
    if (k == keyword.size()) {
      while (j < text.size() && instanceSyntax().isS(text[j]))
	j++;
      if (j == text.size())
	return 1;
    }
  }
  return 0;
}

Boolean Parser::parseLinktypeDeclStart()
{
  if (baseDtd().isNull())
    message(ParserMessages::lpdBeforeBaseDtd);
  unsigned declInputLevel = inputLevel();
  Param parm;
  
  if (!parseParam(allowName, declInputLevel, parm))
    return 0;
  StringC name;
  parm.token.swap(name);
  if (!lookupDtd(name).isNull())
    message(ParserMessages::duplicateDtdLpd, StringMessageArg(name));
  else if (!lookupLpd(name).isNull())
    message(ParserMessages::duplicateLpd, StringMessageArg(name));
  static AllowedParams
    allowSimpleName(Param::indicatedReservedName + Syntax::rSIMPLE,
		    Param::name);
  if (!parseParam(allowSimpleName, declInputLevel, parm))
    return 0;
  Boolean simple;
  Ptr<Dtd> sourceDtd;
  if (parm.type == Param::indicatedReservedName + Syntax::rSIMPLE) {
    simple = 1;
    sourceDtd = baseDtd();
    if (sourceDtd.isNull())
      sourceDtd = new Dtd(StringC(), 1);
  }
  else {
    simple = 0;
    sourceDtd = lookupDtd(parm.token);
    if (sourceDtd.isNull()) {
      message(ParserMessages::noSuchDtd, StringMessageArg(parm.token));
      sourceDtd = new Dtd(parm.token, 0);
    }
  }
  static AllowedParams
    allowImpliedName(Param::indicatedReservedName + Syntax::rIMPLIED,
		     Param::name);
  if (!parseParam(allowImpliedName, declInputLevel, parm))
    return 0;
  Ptr<Dtd> resultDtd;
  Boolean implied = 0;
  if (parm.type == Param::indicatedReservedName + Syntax::rIMPLIED) {
    if (simple) {
      if (!sd().simpleLink())
	message(ParserMessages::simpleLinkFeature);
    }
    else {
      implied = 1;
      if (!sd().implicitLink())
	message(ParserMessages::implicitLinkFeature);
    }
  }
  else {
    if (simple)
      message(ParserMessages::simpleLinkResultNotImplied);
    else {
      if (!sd().explicitLink())
	message(ParserMessages::explicitLinkFeature);
      resultDtd = lookupDtd(parm.token);
      if (resultDtd.isNull())
	message(ParserMessages::noSuchDtd, StringMessageArg(parm.token));
    }
  }
  static AllowedParams
    allowPublicSystemDsoMdc(Param::reservedName + Syntax::rPUBLIC,
			    Param::reservedName + Syntax::rSYSTEM,
			    Param::dso,
			    Param::mdc);
  if (!parseParam(allowPublicSystemDsoMdc, declInputLevel, parm))
    return 0;
  ConstPtr<Entity> entity;
  if (parm.type == Param::reservedName + Syntax::rPUBLIC
      || parm.type == Param::reservedName + Syntax::rSYSTEM) {
    static AllowedParams allowSystemIdentifierDsoMdc(Param::systemIdentifier,
						     Param::dso, Param::mdc);
    ExternalId id;
    if (!parseExternalId(allowSystemIdentifierDsoMdc, allowDsoMdc,
			 1, declInputLevel, parm, id))
      return 0;
    Ptr<Entity> tem
      = new ExternalTextEntity(name, Entity::linktype, markupLocation(), id);
    tem->generateSystemId(*this);
    entity = tem;
#if 0
    eventHandler()
      .externalEntityDecl(new (eventAllocator())
			  ExternalEntityDeclEvent(entity, 0));
#endif
  }
  Ptr<Lpd> lpd;
  if (simple)
    lpd = new SimpleLpd(name, markupLocation(), sourceDtd);
  else
    lpd = new ComplexLpd(name,
			 implied ? Lpd::implicitLink : Lpd::explicitLink,
			 markupLocation(),
			 syntax(),
			 sourceDtd,
			 resultDtd);
  if (!baseDtd().isNull() && shouldActivateLink(name)) {
    size_t nActive = nActiveLink();
    if (simple) {
      size_t nSimple = 0;
      for (size_t i = 0; i < nActive; i++)
	if (activeLpd(i).type() == Lpd::simpleLink)
	  nSimple++;
      if (nSimple == sd().simpleLink())
	message(ParserMessages::simpleLinkCount,
		NumberMessageArg(sd().simpleLink()));
      lpd->activate();
    }
    else {
      Boolean haveImplicit = 0;
      Boolean haveExplicit = 0;
      size_t i;
      for (i = 0; i < nActive; i++) {
	if (activeLpd(i).type() == Lpd::implicitLink)
	  haveImplicit = 1;
	else if (activeLpd(i).type() == Lpd::explicitLink)
	  haveExplicit = 1;
      }
      const Dtd *sourceDtd = lpd->sourceDtd().pointer();
      if (implied && haveImplicit)
	message(ParserMessages::oneImplicitLink);
      else if (sd().explicitLink() <= 1 && sourceDtd != baseDtd().pointer())
	message(sd().explicitLink() == 0
		? ParserMessages::explicitNoRequiresSourceTypeBase
		: ParserMessages::explicit1RequiresSourceTypeBase,
		StringMessageArg(lpd->name()));
      else if (sd().explicitLink() == 1 && haveExplicit && !implied)
	message(ParserMessages::duplicateExplicitChain);
      else if (haveExplicit || haveImplicit
	       || sourceDtd != baseDtd().pointer())
	message(ParserMessages::sorryLink, StringMessageArg(lpd->name()));
      else
	lpd->activate();
    }
  }
  // Discard mdc or dso
  if (currentMarkup())
    currentMarkup()->resize(currentMarkup()->size() - 1);
  eventHandler().startLpd(new (eventAllocator())
			  StartLpdEvent(lpd->active(),
					name,
					entity,
					parm.type == Param::dso,
					markupLocation(),
					currentMarkup()));
  startLpd(lpd);
  if (parm.type == Param::mdc) {
    // unget the mdc
    currentInput()->ungetToken();
    if (entity.isNull()) {
      message(ParserMessages::noLpdSubset, StringMessageArg(name));
      (void)parseLinktypeDeclEnd();
      return 1;
    }
    // reference the entity
    Ptr<EntityOrigin> origin
      = EntityOrigin::make(internalAllocator(), entity, currentLocation());
    entity->dsReference(*this, origin);
    if (inputLevel() == 1) {	// reference failed
      (void)parseLinktypeDeclEnd();
      return 1;
    }
  }
  else if (!entity.isNull())
    setDsEntity(entity);
  setPhase(declSubsetPhase);
  return 1;
}

Boolean Parser::parseLinktypeDeclEnd()
{

  if (defLpd().type() != Lpd::simpleLink) {
    if (!defComplexLpd().initialLinkSet()->defined())
      message(ParserMessages::noInitialLinkSet,
	      StringMessageArg(defLpd().name()));
    ComplexLpd::ConstLinkSetIter iter = defComplexLpd().linkSetIter();
    const LinkSet *linkSet;
    while ((linkSet = iter.next()) != 0)
      if (!linkSet->defined())
	message(ParserMessages::undefinedLinkSet, StringMessageArg(linkSet->name()));
  }
  ConstPtr<Lpd> tem(defLpdPointer());
  endLpd();
  startMarkup(eventsWanted().wantPrologMarkup(), currentLocation());
  Param parm;
  Boolean result = parseParam(allowMdc, inputLevel(), parm);
  eventHandler().endLpd(new (eventAllocator()) EndLpdEvent(tem,
							   markupLocation(),
							   currentMarkup()));
  return result;
}

Boolean Parser::parseLinkDecl()
{
  return parseLinkSet(0);
}

Boolean Parser::parseIdlinkDecl()
{
  return parseLinkSet(1);
}

// This will only get called if we're defining a complex lpd.

Boolean Parser::parseLinkSet(Boolean idlink)
{
  if (defLpd().type() == Lpd::simpleLink) {
    message(idlink ? ParserMessages::idlinkDeclSimple : ParserMessages::linkDeclSimple);
    return 0;
  }
  if (idlink) {
    if (defComplexLpd().hadIdLinkSet())
      message(ParserMessages::duplicateIdLinkSet);
    else
      defComplexLpd().setHadIdLinkSet();
  }
  unsigned declInputLevel = inputLevel();
  Param parm;
  
  Boolean isExplicit = (defLpd().type() == Lpd::explicitLink);
  LinkSet *linkSet;
  if (idlink) {
    if (!parseParam(allowName, declInputLevel, parm))
      return 0;
    linkSet = 0;
  }
  else {
    static AllowedParams
      allowNameInitial(Param::name,
		       Param::indicatedReservedName + Syntax::rINITIAL);
    if (!parseParam(allowNameInitial, declInputLevel, parm))
      return 0;
    if (parm.type == Param::name)
      linkSet = lookupCreateLinkSet(parm.token);
    else
      linkSet = defComplexLpd().initialLinkSet();
    if (linkSet->defined())
      message(ParserMessages::duplicateLinkSet, StringMessageArg(linkSet->name()));
    static AllowedParams
      allowExplicitLinkRule(Param::name,
			    Param::nameGroup,
			    Param::indicatedReservedName + Syntax::rIMPLIED);
    if (!parseParam(isExplicit ? allowExplicitLinkRule : allowNameNameGroup,
		    declInputLevel, parm))
      return 0;
  }

  do {
    StringC id;
    if (idlink) {
      parm.token.swap(id);
      if (!parseParam(isExplicit ? allowExplicitLinkRuleMdc : allowNameNameGroupMdc,
		      declInputLevel, parm))
	return 0;
    }
    if (parm.type == Param::indicatedReservedName + Syntax::rIMPLIED) {
      if (!parseParam(allowName, declInputLevel, parm))
	return 0;
      Boolean resultImplied;
      const ElementType *resultType;
      AttributeList resultAttributes;
      if (!parseResultElementSpec(declInputLevel,
				  parm,
				  idlink,
				  resultImplied,
				  resultType,
				  resultAttributes))
	return 0;
      if (resultType) {
	const AttributeList *dummy;
	if (linkSet->impliedResultAttributes(resultType, dummy))
	  message(ParserMessages::duplicateImpliedResult,
		  StringMessageArg(resultType->name()));
	else
	  linkSet->addImplied(resultType, resultAttributes);
      }
    }
    else {
      SourceLinkRule *linkRule = 0;
      IdLinkRule idLinkRule;
      Ptr<SourceLinkRuleResource> linkRuleResource;
      if (idlink)
	linkRule = &idLinkRule;
      else {
	linkRuleResource = new SourceLinkRuleResource;
	linkRule = linkRuleResource.pointer();
      }
      Vector<const ElementType *> assocElementTypes;
      if (parm.type == Param::name) {
	assocElementTypes.resize(1);
	assocElementTypes[0] = lookupCreateElement(parm.token);
      }
      else {
	assocElementTypes.resize(parm.nameTokenVector.size());
	for (size_t i = 0; i < assocElementTypes.size(); i++)
	  assocElementTypes[i]
	    = lookupCreateElement(parm.nameTokenVector[i].name);
      }
      static AllowedParams
	allow2i(Param::indicatedReservedName + Syntax::rUSELINK,
		Param::indicatedReservedName + Syntax::rPOSTLINK,
		Param::dso,
		Param::mdc,
		Param::name,
		Param::nameGroup);
      static AllowedParams
	allow2id(Param::indicatedReservedName + Syntax::rUSELINK,
		 Param::indicatedReservedName + Syntax::rPOSTLINK,
		 Param::dso,
		 Param::mdc,
		 Param::name);
      static AllowedParams
	allow2e(Param::indicatedReservedName + Syntax::rUSELINK,
		Param::indicatedReservedName + Syntax::rPOSTLINK,
		Param::dso,
		Param::name,
		Param::indicatedReservedName + Syntax::rIMPLIED);

      if (!parseParam(isExplicit
		      ? allow2e
		      : (idlink ? allow2id : allow2i), declInputLevel, parm))
	return 0;
      if (parm.type == Param::indicatedReservedName + Syntax::rUSELINK) {
	static AllowedParams
	  allowLinkSetEmpty(Param::name,
			    Param::indicatedReservedName + Syntax::rINITIAL,
			    Param::indicatedReservedName + Syntax::rEMPTY);
	if (!parseParam(allowLinkSetEmpty, declInputLevel, parm))
	  return 0;
	const LinkSet *uselink;
	if (parm.type == Param::name)
	  uselink = lookupCreateLinkSet(parm.token);
	else if (parm.type == Param::indicatedReservedName + Syntax::rINITIAL)
	  uselink = defComplexLpd().initialLinkSet();
	else
	  uselink = defComplexLpd().emptyLinkSet();
	linkRule->setUselink(uselink);
	static AllowedParams
	  allow3i(Param::indicatedReservedName + Syntax::rPOSTLINK,
		  Param::dso,
		  Param::mdc,
		  Param::name,
		  Param::nameGroup);
	static AllowedParams
	  allow3id(Param::indicatedReservedName + Syntax::rPOSTLINK,
		   Param::dso,
		   Param::mdc,
		   Param::name);
	static AllowedParams
	  allow3e(Param::indicatedReservedName + Syntax::rPOSTLINK,
		  Param::dso,
		  Param::name,
		  Param::indicatedReservedName + Syntax::rIMPLIED);
	
	if (!parseParam(isExplicit
			? allow3e
			: (idlink ? allow3id : allow3i),
			declInputLevel, parm))
	  return 0;
      }
      if (parm.type == Param::indicatedReservedName + Syntax::rPOSTLINK) {
	if (!parseParam(allowLinkSetSpec, declInputLevel, parm))
	  return 0;
	const LinkSet *postlink;
	if (parm.type == Param::indicatedReservedName + Syntax::rRESTORE)
	  linkRule->setPostlinkRestore();
	else {
	  if (parm.type == Param::name)
	    postlink = lookupCreateLinkSet(parm.token);
	  else if (parm.type
		   == Param::indicatedReservedName + Syntax::rINITIAL)
	    postlink = defComplexLpd().initialLinkSet();
	  else
	    postlink = defComplexLpd().emptyLinkSet();
	  linkRule->setPostlink(postlink);
	}
	static AllowedParams
	  allow4i(Param::dso,
		  Param::mdc,
		  Param::name,
		  Param::nameGroup);
	static AllowedParams
	  allow4id(Param::dso,
		   Param::mdc,
		   Param::name);
	static AllowedParams
	  allow4e(Param::dso,
		  Param::name,
		  Param::indicatedReservedName + Syntax::rIMPLIED);
	if (!parseParam(isExplicit
			? allow4e
			: (idlink ? allow4id : allow4i),
			declInputLevel, parm))
	  return 0;
      }
      AttributeList attributes;
      ConstPtr<AttributeDefinitionList> attDef;
      for (size_t i = 0; i < assocElementTypes.size(); i++) {
	const ElementType *e = assocElementTypes[i];
	if (e) {
	  if (i == 0)
	    attDef = defComplexLpd().attributeDef(e);
	  else if (attDef != defComplexLpd().attributeDef(e))
	    message(ParserMessages::assocElementDifferentAtts);
	  // FIXME recover from this
	}
      }
      attributes.init(attDef);
      
      if (parm.type == Param::dso) {
	Boolean netEnabling;
	Ptr<AttributeDefinitionList> newAttDef;
	if (!parseAttributeSpec(1, attributes, netEnabling, newAttDef))
	  return 0;
	if (!newAttDef.isNull()) {
	  newAttDef->setIndex(defComplexLpd().allocAttributeDefinitionListIndex());
	  for (size_t i = 0; i < assocElementTypes.size(); i++) {
	    const ElementType *e = assocElementTypes[i];
	    if (e && defComplexLpd().attributeDef(e) == attDef)
	      defComplexLpd().setAttributeDef(e, newAttDef);
	  }
	}
	static AllowedParams
	  allow5e(Param::name,
		  Param::indicatedReservedName + Syntax::rIMPLIED);
	if (!parseParam(isExplicit
			? allow5e
			: (idlink ? allowNameMdc : allowNameNameGroupMdc),
			declInputLevel, parm))
	  return 0;
      }
      else
	attributes.finish(*this);
      linkRule->setLinkAttributes(attributes);
      if (isExplicit) {
	Boolean resultImplied;
	const ElementType *resultType;
	AttributeList resultAttributes;
	if (!parseResultElementSpec(declInputLevel,
				    parm,
				    idlink,
				    resultImplied,
				    resultType,
				    resultAttributes))
	  return 0;
	if (!resultImplied)
	  linkRule->setResult(resultType, resultAttributes);
      }
      // Install the link rule.
      if (idlink) {
	idLinkRule.setAssocElementTypes(assocElementTypes);
	addIdLinkRule(id, idLinkRule);
      }
      else {
	if (!linkSet->defined()) {
	  for (size_t i = 0; i < assocElementTypes.size(); i++)
	    if (assocElementTypes[i])
	      addLinkRule(linkSet, assocElementTypes[i], linkRuleResource);
	}
      }
    }
  } while (parm.type != Param::mdc);
  if (linkSet)
    linkSet->setDefined();
  if (currentMarkup()) {
    if (idlink)
      eventHandler().idLinkDecl(new (eventAllocator())
				IdLinkDeclEvent(defComplexLpdPointer(),
						markupLocation(),
						currentMarkup()));
    else
      eventHandler().linkDecl(new (eventAllocator())
			      LinkDeclEvent(linkSet,
					    defComplexLpdPointer(),
					    markupLocation(),
					    currentMarkup()));
  }
  return 1;
}

void Parser::addIdLinkRule(const StringC &id,
			   IdLinkRule &rule)
{
  IdLinkRuleGroup *group = defComplexLpd().lookupCreateIdLink(id);
  size_t nRules = group->nLinkRules();
  if ((nRules == 1 && group->linkRule(0).attributes().nSpec() == 0)
      || nRules >= 1 && rule.attributes().nSpec() == 0)
    message(ParserMessages::multipleIdLinkRuleAttribute,
	    StringMessageArg(id));
  group->addLinkRule(rule);
}

void Parser::addLinkRule(LinkSet *linkSet,
			 const ElementType *sourceElement,
			 const ConstPtr<SourceLinkRuleResource> &linkRule)
{
  size_t nRules = linkSet->nLinkRules(sourceElement);
  if ((nRules == 1
       && linkSet->linkRule(sourceElement, 0).attributes().nSpec() == 0)
      || nRules >= 1 && linkRule->attributes().nSpec() == 0)
    message(ParserMessages::multipleLinkRuleAttribute,
	    StringMessageArg(sourceElement->name()));
  linkSet->addLinkRule(sourceElement, linkRule);
}

class ResultAttributeSpecModeSetter {
public:
  ResultAttributeSpecModeSetter(ParserState *state) : state_(state) {
    state_->setResultAttributeSpecMode();
  }
  ~ResultAttributeSpecModeSetter() { clear(); }
  void clear() {
    if (state_) {
      state_->clearResultAttributeSpecMode();
      state_ = 0;
    }
  }
private:
  ParserState *state_;
};

Boolean Parser::parseResultElementSpec(unsigned declInputLevel,
				       Param &parm,
				       Boolean idlink,
				       Boolean &implied,
				       const ElementType *&resultType,
				       AttributeList &attributes)
{
  if (parm.type == Param::indicatedReservedName + Syntax::rIMPLIED) {
    if (!parseParam(idlink ? allowNameMdc : allowExplicitLinkRuleMdc,
		    declInputLevel, parm))
      return 0;
    implied = 1;
  }
  else {
    implied = 0;
    ElementType *e = lookupResultElementType(parm.token);
    resultType = e;
    static AllowedParams
      allow(Param::dso,
	    Param::mdc,
	    Param::name,
	    Param::nameGroup,
	    Param::indicatedReservedName + Syntax::rIMPLIED);
    static AllowedParams
      allowNameDsoMdc(Param::dso,
		      Param::mdc,
		      Param::name);
    if (!parseParam(idlink ? allowNameDsoMdc : allow,
		    declInputLevel, parm))
      return 0;
    ConstPtr<AttributeDefinitionList> attDef;
    if (e)
      attDef = e->attributeDef();
    attributes.init(attDef);
    if (parm.type == Param::dso) {
      ResultAttributeSpecModeSetter modeSetter(this);
      Boolean netEnabling;
      Ptr<AttributeDefinitionList> newAttDef;
      if (!parseAttributeSpec(1, attributes, netEnabling, newAttDef))
	return 0;
      if (!newAttDef.isNull()) {
	Ptr<Dtd> r(defComplexLpd().resultDtd());
	if (!r.isNull()) {
	  newAttDef->setIndex(r->allocAttributeDefinitionListIndex());
	  if (e)
	    e->setAttributeDef(newAttDef);
	}
      }
      modeSetter.clear();
      if (attributes.nSpec() == 0)
	message(ParserMessages::emptyResultAttributeSpec);
      if (!parseParam(idlink ? allowNameMdc : allowExplicitLinkRuleMdc,
		      declInputLevel, parm))
	return 0;
    }
    else {
      // For entity and notation attributes.
      ResultAttributeSpecModeSetter modeSetter(this);
      attributes.finish(*this);
      modeSetter.clear();
    }
  }
  return 1;
}

ElementType *Parser::lookupResultElementType(const StringC &name)
{
  Dtd *dtd = defComplexLpd().resultDtd().pointer();
  if (!dtd)
    return 0;
  ElementType *e = dtd->lookupElementType(name);
  if (!e)
    message(ParserMessages::noSuchResultElement, StringMessageArg(name));
  return e;
}

Boolean Parser::parseUselinkDecl()
{
  unsigned declInputLevel = inputLevel();
  Param parm;
  if (!parseParam(allowLinkSetSpec, declInputLevel, parm))
    return 0;
  Param parm2;
  if (!parseParam(allowName, declInputLevel, parm2))
    return 0;
  StringC linkType;
  parm2.token.swap(linkType);
  if (!parseParam(allowMdc, declInputLevel, parm2))
    return 0;
  ConstPtr<Lpd> lpd = lookupLpd(linkType);
  if (lpd.isNull()) 
    message(ParserMessages::uselinkBadLinkType, StringMessageArg(linkType));
  else if (lpd->type() == Lpd::simpleLink)
    message(ParserMessages::uselinkSimpleLpd, StringMessageArg(linkType));
  else {
    const ComplexLpd *complexLpd = (const ComplexLpd *)lpd.pointer();
    const LinkSet *linkSet;
    Boolean restore = 0;
    if (parm.type == Param::name) {
      linkSet = complexLpd->lookupLinkSet(parm.token);
      if (!linkSet) {
	message(ParserMessages::uselinkBadLinkSet,
		StringMessageArg(complexLpd->name()),
		StringMessageArg(parm.token));
	return 1;
      }
    }
    else if (parm.type == Param::indicatedReservedName + Syntax::rINITIAL)
      linkSet = complexLpd->initialLinkSet();
    else if (parm.type == Param::indicatedReservedName + Syntax::rEMPTY)
      linkSet = complexLpd->emptyLinkSet();
    else {
      linkSet = 0;
      restore = 1;
    }
    if (lpd->active())
      eventHandler().uselink(new (eventAllocator())
			     UselinkEvent(lpd, linkSet,
					  restore, markupLocation(),
					  currentMarkup()));
    else
      eventHandler().ignoredMarkup(new (eventAllocator())
				   IgnoredMarkupEvent(markupLocation(),
						      currentMarkup()));
  }
  return 1;
}

LinkSet *Parser::lookupCreateLinkSet(const StringC &name)
{
  LinkSet *linkSet = defComplexLpd().lookupLinkSet(name);
  if (!linkSet) {
    linkSet = new LinkSet(name, defComplexLpd().sourceDtd().pointer());
    defComplexLpd().insertLinkSet(linkSet);
  }
  return linkSet;
}

Boolean Parser::parseMarkedSectionDeclStart()
{
  if (markedSectionLevel() == syntax().taglvl())
    message(ParserMessages::markedSectionLevel,
	    NumberMessageArg(syntax().taglvl()));
  if (!inInstance()
      && options().warnInternalSubsetMarkedSection
      && inputLevel() == 1)
    message(ParserMessages::internalSubsetMarkedSection);
  if (markedSectionSpecialLevel() > 0) {
    startMarkedSection(markupLocation());
    if (inInstance()
	? eventsWanted().wantMarkedSections()
	: eventsWanted().wantPrologMarkup())
      eventHandler().ignoredChars(new (eventAllocator())
				  IgnoredCharsEvent(currentInput()->currentTokenStart(),
						    currentInput()->currentTokenLength(),
						    currentLocation(),
						    0));
				  
    return 1;
  }
  Boolean discardMarkup;
  if (startMarkup(inInstance()
		  ? eventsWanted().wantMarkedSections()
		  : eventsWanted().wantPrologMarkup(),
		  currentLocation())) {
    currentMarkup()->addDelim(Syntax::dMDO);
    currentMarkup()->addDelim(Syntax::dDSO);
    discardMarkup = 0;
  }
  else if (options().warnInstanceStatusKeywordSpecS && inInstance()) {
    startMarkup(1, currentLocation());
    discardMarkup = 1;
  }
  unsigned declInputLevel = inputLevel();
  static AllowedParams allowStatusDso(Param::dso,
				      Param::reservedName + Syntax::rCDATA,
				      Param::reservedName + Syntax::rRCDATA,
				      Param::reservedName + Syntax::rIGNORE,
				      Param::reservedName + Syntax::rINCLUDE,
				      Param::reservedName + Syntax::rTEMP);
  Param parm;
  MarkedSectionEvent::Status status = MarkedSectionEvent::include;
  if (!parseParam(allowStatusDso, declInputLevel, parm))
    return 0;
  if (options().warnMissingStatusKeyword && parm.type == Param::dso)
    message(ParserMessages::missingStatusKeyword);
  while (parm.type != Param::dso) {
    switch (parm.type) {
    case Param::reservedName + Syntax::rCDATA:
      if (status < MarkedSectionEvent::cdata)
	status = MarkedSectionEvent::cdata;
      break;
    case Param::reservedName + Syntax::rRCDATA:
      if (status < MarkedSectionEvent::rcdata)
	status = MarkedSectionEvent::rcdata;
      if (options().warnRcdataMarkedSection)
	message(ParserMessages::rcdataMarkedSection);
      break;
    case Param::reservedName + Syntax::rIGNORE:
      if (status < MarkedSectionEvent::ignore)
	status = MarkedSectionEvent::ignore;
      if (inInstance() && options().warnInstanceIgnoreMarkedSection)
	message(ParserMessages::instanceIgnoreMarkedSection);
      break;
    case Param::reservedName + Syntax::rINCLUDE:
      if (inInstance() && options().warnInstanceIncludeMarkedSection)
	message(ParserMessages::instanceIncludeMarkedSection);
      break;
    case Param::reservedName + Syntax::rTEMP:
      if (options().warnTempMarkedSection)
	message(ParserMessages::tempMarkedSection);
      break;
    }
    if (!parseParam(allowStatusDso, declInputLevel, parm))
      return 0;
    if (options().warnMultipleStatusKeyword
        && parm.type != Param::dso)
      message(ParserMessages::multipleStatusKeyword);
  }
  // FIXME this disallows
  // <!entity % e "include [ stuff ">
  // ...
  // <![ %e; ]]>
  // which I think is legal.

  if (inputLevel() > declInputLevel)
    message(ParserMessages::parameterEntityNotEnded);
  switch (status) {
  case MarkedSectionEvent::include:
    startMarkedSection(markupLocation());
    break;
  case MarkedSectionEvent::cdata:
    startSpecialMarkedSection(cmsMode, markupLocation());
    break;
  case MarkedSectionEvent::rcdata:
    startSpecialMarkedSection(rcmsMode, markupLocation());
    break;
  case MarkedSectionEvent::ignore:
    startSpecialMarkedSection(imsMode, markupLocation());
    break;
  }
  if (currentMarkup()) {
    if (options().warnInstanceStatusKeywordSpecS && inInstance()) {
      Location loc(markupLocation());
      for (MarkupIter iter(*currentMarkup()); iter.valid(); iter.advance(loc, syntaxPointer())) {
	if (iter.type() == Markup::s) {
	  setNextLocation(loc);
	  message(ParserMessages::instanceStatusKeywordSpecS);
	}
      }
      if (discardMarkup)
	startMarkup(0, markupLocation());
    }
    eventHandler().markedSectionStart(new (eventAllocator())
				      MarkedSectionStartEvent(status,
							      markupLocation(),
							      currentMarkup()));
  }
  return 1;
}

void Parser::handleMarkedSectionEnd()
{
  if (markedSectionLevel() == 0)
    message(ParserMessages::markedSectionEnd);
  else {
    if (inInstance()
	? eventsWanted().wantMarkedSections()
	: eventsWanted().wantPrologMarkup()) {
      if (markedSectionSpecialLevel() > 1)
	eventHandler().ignoredChars(new (eventAllocator())
				    IgnoredCharsEvent(currentInput()->currentTokenStart(),
						      currentInput()->currentTokenLength(),
						      currentLocation(),
						      0));
      else {
	MarkedSectionEvent::Status status;
	switch (currentMode()) {
	case cmsMode:
	  status = MarkedSectionEvent::cdata;
	  break;
	case rcmsMode:
	  status = MarkedSectionEvent::rcdata;
	  break;
	case imsMode:
	  status = MarkedSectionEvent::ignore;
	  break;
	default:
	  status = MarkedSectionEvent::include;
	  break;
	}
	startMarkup(1, currentLocation());
	currentMarkup()->addDelim(Syntax::dMSC);
	currentMarkup()->addDelim(Syntax::dMDC);
	eventHandler().markedSectionEnd(new (eventAllocator())
					MarkedSectionEndEvent(status,
							      markupLocation(),
							      currentMarkup()));
      }
    }
    endMarkedSection();
  }
}

void Parser::emptyCommentDecl()
{
  if (startMarkup(eventsWanted().wantCommentDecls(), currentLocation())) {
    currentMarkup()->addDelim(Syntax::dMDO);
    currentMarkup()->addDelim(Syntax::dMDC);
    eventHandler().commentDecl(new (eventAllocator())
			       CommentDeclEvent(markupLocation(),
						currentMarkup()));
  }
  if (options().warnEmptyCommentDecl)
    message(ParserMessages::emptyCommentDecl);
}

Boolean Parser::parseCommentDecl()
{
  if (startMarkup(inInstance()
		  ? eventsWanted().wantCommentDecls()
		  : eventsWanted().wantPrologMarkup(),
		  currentLocation()))
    currentMarkup()->addDelim(Syntax::dMDO);
  if (!parseComment(comMode))
    return 0;
  for (;;) {
    Token token = getToken(mdMode);
    switch (token) {
    case tokenS:
      if (currentMarkup())
	currentMarkup()->addS(currentChar());
      if (options().warnCommentDeclS)
	message(ParserMessages::commentDeclS);
      break;
    case tokenCom:
      if (!parseComment(comMode))
	return 0;
      if (options().warnCommentDeclMultiple)
	message(ParserMessages::commentDeclMultiple);
      break;
    case tokenMdc:
      if (currentMarkup())
	currentMarkup()->addDelim(Syntax::dMDC);
      goto done;
    case tokenEe:
      message(ParserMessages::declarationLevel);
      return 0;
    case tokenUnrecognized:
      if (reportNonSgmlCharacter())
	break;
      // braces to work round Sun C++ 4.0 bug
      {
	message(ParserMessages::commentDeclarationCharacter,
		StringMessageArg(currentToken()),
		markupLocation());
      }
      return 0;
    default:
      // braces to work round Sun C++ 4.0 bug
      {
	message(ParserMessages::commentDeclInvalidToken,
		TokenMessageArg(token, mdMode, syntaxPointer(), sdPointer()),
		markupLocation());
      }
      return 0;
    }
  }
 done:
  if (currentMarkup())
    eventHandler().commentDecl(new (eventAllocator())
			       CommentDeclEvent(markupLocation(),
						currentMarkup()));
  return 1;
}

Boolean Parser::parseAfdrDecl()
{
  unsigned declInputLevel = inputLevel();
  static AllowedParams allowMinimumLiteral(Param::minimumLiteral);
  Param parm;
  setHadAfdrDecl();
  if (!parseParam(allowMinimumLiteral, declInputLevel, parm))
    return 0;
  if (parm.literalText.string() != sd().execToInternal("ISO/IEC 10744:1997"))
    message(ParserMessages::afdrVersion,
	    StringMessageArg(parm.literalText.string()));
  if (!parseParam(allowMdc, declInputLevel, parm))
    return 0;
  eventHandler().ignoredMarkup(new (eventAllocator())
			       IgnoredMarkupEvent(markupLocation(),
						  currentMarkup()));
  return 1;
}

#ifdef SP_NAMESPACE
}
#endif
