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

#ifdef __GNUG__
#pragma implementation
#endif

#include "splib.h"
#include "CharsetInfo.h"
#include "MessageArg.h"
#include "CatalogMessages.h"
#include "SOEntityCatalog.h"
#include "EntityDecl.h"
#include "EntityCatalog.h"
#include "Message.h"
#include "StringC.h"
#include "types.h"
#include "HashTable.h"
#include "InputSource.h"
#include "Boolean.h"
#include "SubstTable.h"
#include "CatalogEntry.h"
#include "Vector.h"
#include "StorageManager.h"
#include "macros.h"

#ifdef SP_NAMESPACE
namespace SP_NAMESPACE {
#endif

class CatalogParser;
class SOEntityCatalog;

class SOCatalogManagerImpl : public SOCatalogManager {
public:
  SOCatalogManagerImpl(const Vector<StringC> &sysids,
		       size_t nSysidsMustExist,
		       const CharsetInfo *sysidCharset,
		       const CharsetInfo *catalogCharset,
		       Boolean useDocCatalog);
  ConstPtr<EntityCatalog> makeCatalog(StringC &systemId,
				      const CharsetInfo &charset,
				      ExtendEntityManager *,
				      Messenger &) const;
  Boolean mapCatalog(ParsedSystemId &systemId,
		     ExtendEntityManager *em,
		     Messenger &mgr) const;
private:
  void addCatalogsForDocument(CatalogParser &parser,
			      StringC &sysid,
			      SOEntityCatalog *,
			      const CharsetInfo &charset,
			      Messenger &mgr) const;
  size_t nSystemCatalogsMustExist_;
  Vector<StringC> systemCatalogs_;
  const CharsetInfo *sysidCharset_;
  const CharsetInfo *catalogCharset_;
  Boolean useDocCatalog_;
};

class SOEntityCatalog : public EntityCatalog {
public:
  SOEntityCatalog(Ptr<ExtendEntityManager> em);
  typedef EntityDecl::DeclType DeclType;
  Boolean document(const CharsetInfo &, Messenger &, StringC &) const;
  Boolean sgmlDecl(const CharsetInfo &, Messenger &, StringC &) const;
  Boolean lookup(const EntityDecl &entity,
		 const Syntax &,
		 const CharsetInfo &,
		 Messenger &,
		 StringC &) const;
  Boolean lookupPublic(const StringC &,
		       const CharsetInfo &,
		       Messenger &,
		       StringC &) const;
  Boolean lookupChar(const StringC &,
                     const CharsetInfo &,
		     Messenger &,
		     UnivChar &) const;
  void addPublicId(StringC &publicId, StringC &systemId, const Location &,
		   Boolean override);
  void addDelegate(StringC &prefix, StringC &systemId, const Location &,
		   Boolean override);
  void addSystemId(StringC &systemId, StringC &replSystemId, const Location &);
  void addName(StringC &name, DeclType, StringC &systemId, const Location &,
	       Boolean override);
  void setSgmlDecl(StringC &str, const Location &loc);
  void setDocument(StringC &str, const Location &loc);
  void setBase(const Location &loc);
  void endCatalog();
  const Ptr<ExtendEntityManager> &entityManager() {
    return em_;
  }
private:
  SOEntityCatalog(const SOEntityCatalog &);	// undefined
  void operator=(const SOEntityCatalog &);	// undefined

  Boolean expandCatalogSystemId(const StringC &str,
				const Location &loc,
				size_t baseNumber,
				Boolean isNdata,
				const CharsetInfo &charset,
				const StringC *lookupPublicId,
				Messenger &mgr,
				StringC &result) const;
  const CatalogEntry *
    findBestPublicEntry(const StringC &publicId, Boolean overrideOnly,
			const CharsetInfo &charset, Boolean &delegated) const;

  class Table {
  public:
    Table();
    const CatalogEntry *lookup(const StringC &, Boolean overrideOnly) const;
    const CatalogEntry *lookup(const StringC &key,
			       const SubstTable<Char> &substTable,
			       Boolean overrideOnly) const;
    void insert(const StringC &, const CatalogEntry &, Boolean override);
    size_t count() const;
  private:
    Table(const Table &);	// undefined
    void operator=(const Table &); // undefined
    // These are entries that are applicable when an explicit system id
    // was specified in the external identifier.
    HashTable<StringC,CatalogEntry> overrideEntries_;
    // This specifies the entries that should substitute for the
    // overrideEntries_ when an explicit system identifier was not specified.
    HashTable<StringC,CatalogEntry> normalEntries_;
  };

  Table publicIds_;
  Table delegates_;
  HashTable<StringC,CatalogEntry> systemIds_;
  Table names_[5];
  size_t catalogNumber_;
  Boolean haveSgmlDecl_;
  StringC sgmlDecl_;
  Location sgmlDeclLoc_;
  size_t sgmlDeclBaseNumber_;
  StringC document_;
  Boolean haveDocument_;
  Location documentLoc_;
  size_t documentBaseNumber_;
  Boolean haveCurrentBase_;
  Vector<Location> base_;
  Ptr<ExtendEntityManager> em_;
};

class CatalogParser : private Messenger {
public:
  CatalogParser(const CharsetInfo &);
  void parseCatalog(const StringC &sysid,
		    Boolean mustExist,
		    const CharsetInfo &sysidCharset,
		    const CharsetInfo &catalogCharset,
		    InputSourceOrigin *origin,
		    SOEntityCatalog *catalog,
		    Messenger &mgr);
public:
  // Since it's a return type, it has to be public to keep some 
  // (broken) compilers happy.
  enum Param {
    eofParam,
    literalParam,
    nameParam,
    percentParam
  };
private:
  enum {
    data,
    eof,
    nul,
    lit,
    lita,
    minus,
    s,
    min				// other minimum data characters
  };
  enum { minimumLiteral = 01 };

  Messenger &messenger() { return *this; }
  void dispatchMessage(Message &);
  void dispatchMessage(const Message &);
  void initMessage(Message &);
  void parsePublic();
  void parseDelegate();
  void parseDtddecl();
  void parseSystem();
  void parseNameMap(EntityDecl::DeclType declType);
  void parseOverride();
  Param parseParam(unsigned flags = 0);
  Boolean parseArg();
  void parseLiteral(Char delim, unsigned flags);
  void parseName();
  void skipComment();
  void upcase(StringC &);
  Boolean inLoop(const Location &loc);
  Boolean isMinimumData(Xchar c) {
    int cat = categoryTable_[c];
    return (cat == min || (cat == s && c != tab_)
	    || cat == minus || cat == lita);
  }
  Xchar get() { return in_->get(messenger()); }
  void unget() { in_->ungetToken(); }
  Messenger *mgr_;
  InputSource *in_;
  SOEntityCatalog *catalog_;
  StringC param_;
  Location paramLoc_;
  Char minus_;
  Char tab_;
  Char rs_;
  Char re_;
  Char space_;
  StringC publicKey_;
  StringC systemKey_;
  StringC entityKey_;
  StringC doctypeKey_;
  StringC linktypeKey_;
  StringC notationKey_;
  StringC overrideKey_;
  StringC sgmlDeclKey_;
  StringC documentKey_;
  StringC catalogKey_;
  StringC yesKey_;
  StringC noKey_;
  StringC baseKey_;
  StringC delegateKey_;
  StringC dtddeclKey_;
  StringC sgmlKey_;
  XcharMap<unsigned char> categoryTable_;
  SubstTable<Char> substTable_;
  Boolean override_;
};

ExtendEntityManager::CatalogManager *
SOCatalogManager::make(const Vector<StringC> &sysids,
		       size_t nSysidsMustExist,
		       const CharsetInfo *sysidCharset,
		       const CharsetInfo *catalogCharset,
		       Boolean useDocCatalog)
{
  return new SOCatalogManagerImpl(sysids,
				  nSysidsMustExist,
				  sysidCharset,
				  catalogCharset,
				  useDocCatalog);
}
		       
SOCatalogManagerImpl::SOCatalogManagerImpl(const Vector<StringC> &systemCatalogs,
					   size_t nSystemCatalogsMustExist,
					   const CharsetInfo *sysidCharset,
					   const CharsetInfo *catalogCharset,
					   Boolean useDocCatalog)
: systemCatalogs_(systemCatalogs),
  nSystemCatalogsMustExist_(nSystemCatalogsMustExist),
  sysidCharset_(sysidCharset),
  catalogCharset_(catalogCharset),
  useDocCatalog_(useDocCatalog)
{
}

Boolean SOCatalogManagerImpl::mapCatalog(ParsedSystemId &systemId,
					 ExtendEntityManager *em,
					 Messenger &mgr) const
{
  Vector<ParsedSystemId::Map> maps;
  systemId.maps.swap(maps);
  while (maps.size() > 0) {
    StringC catalogSystemId;
    systemId.unparse(*sysidCharset_, 0, catalogSystemId);
    SOEntityCatalog *catalog = new SOEntityCatalog(em);
    ConstPtr<EntityCatalog> deleter(catalog);
    CatalogParser parser(*catalogCharset_);
    parser.parseCatalog(catalogSystemId, 1, *sysidCharset_, *catalogCharset_,
			InputSourceOrigin::make(), catalog, mgr);
    // FIXME do catalog caching here
    StringC s;
    if (maps.back().type == ParsedSystemId::Map::catalogDocument) {
      if (!catalog->document(*sysidCharset_, mgr, s)) {
	mgr.message(CatalogMessages::noDocumentEntry,
		    StringMessageArg(catalogSystemId));
	return 0;
      }
    }
    else {
      ASSERT(maps.back().type == ParsedSystemId::Map::catalogPublic);
      if (!catalog->lookupPublic(maps.back().publicId, *sysidCharset_, mgr,
				 s)) {
	mgr.message(CatalogMessages::noPublicEntry,
		    StringMessageArg(maps.back().publicId),
		    StringMessageArg(catalogSystemId));
	return 0;
      }
    }
    ParsedSystemId tem;
    if (!em->parseSystemId(s, *sysidCharset_, 0, 0, mgr, tem))
      return 0;
    systemId = tem;
    maps.resize(maps.size() - 1);
    for (size_t i = 0; i < systemId.maps.size(); i++)
      maps.push_back(systemId.maps[i]);
    systemId.maps.clear();
  }
  return 1;
}

ConstPtr<EntityCatalog>
SOCatalogManagerImpl::makeCatalog(StringC &systemId,
				  const CharsetInfo &charset,
				  ExtendEntityManager *em,
				  Messenger &mgr) const
{
  SOEntityCatalog *entityCatalog = new SOEntityCatalog(em);
  CatalogParser parser(*catalogCharset_);
  size_t i;
  for (i = 0; i < nSystemCatalogsMustExist_; i++)
    parser.parseCatalog(systemCatalogs_[i], 1,
			*sysidCharset_, *catalogCharset_,
			InputSourceOrigin::make(), entityCatalog,
			mgr);
  if (useDocCatalog_)
    addCatalogsForDocument(parser, systemId, entityCatalog, charset, mgr);
  for (i = nSystemCatalogsMustExist_; i < systemCatalogs_.size(); i++)
    parser.parseCatalog(systemCatalogs_[i], 0,
			*sysidCharset_, *catalogCharset_,
			InputSourceOrigin::make(), entityCatalog,
			mgr);

  return entityCatalog;
}


void SOCatalogManagerImpl::addCatalogsForDocument(CatalogParser &parser,
						  StringC &sysid,
						  SOEntityCatalog *impl,
						  const CharsetInfo &charset,
						  Messenger &mgr) const
{
  ParsedSystemId v;
  if (!impl->entityManager()->parseSystemId(sysid, charset, 0, 0, mgr, v))
    return;
  if (v.maps.size() > 0) {
    if (v.maps[0].type == ParsedSystemId::Map::catalogDocument) {
      v.maps.erase(v.maps.begin(), v.maps.begin() + 1);
      StringC tem;
      v.unparse(charset, 0, tem);
      parser.parseCatalog(tem, 1, charset, *catalogCharset_,
			  InputSourceOrigin::make(), impl, mgr);
      if (!impl->document(charset, mgr, sysid)) {
	mgr.message(CatalogMessages::noDocumentEntry, StringMessageArg(tem));
	sysid.resize(0);
      }
    }
    return;
  }
  Vector<StringC> catalogs;
  size_t i;
  for (i = 0; i < v.size(); i++)
    if (v[i].storageManager->inheritable()) {
      ParsedSystemId catalogId;
      catalogId.resize(1);
      StorageObjectSpec &spec = catalogId.back();
      spec.storageManager = v[i].storageManager;
      spec.codingSystemType = v[i].codingSystemType;
      spec.codingSystemName = v[i].codingSystemName;
      spec.specId = spec.storageManager->idCharset()->execToDesc("catalog");
      spec.storageManager->resolveRelative(v[i].specId, spec.specId, 0);
      spec.baseId = v[i].baseId;
      spec.records = v[i].records;
      StringC tem;
      catalogId.unparse(charset, 0, tem);
      for (size_t j = 0; j < catalogs.size(); j++)
	if (tem == catalogs[j]) {
	  tem.resize(0);
	  break;
	}
      if (tem.size() > 0) {
	catalogs.resize(catalogs.size() + 1);
	tem.swap(catalogs.back());
      }
    }
  for (i = 0; i < catalogs.size(); i++)
    parser.parseCatalog(catalogs[i], 0, charset,
			*catalogCharset_, InputSourceOrigin::make(), impl,
			mgr);
}

SOEntityCatalog::SOEntityCatalog(Ptr<ExtendEntityManager> em)
: em_(em), catalogNumber_(0), haveSgmlDecl_(0), haveDocument_(0),
  haveCurrentBase_(0)
{
}

void SOEntityCatalog::endCatalog()
{
  catalogNumber_++;
  haveCurrentBase_ = 0;
}

Boolean SOEntityCatalog::expandCatalogSystemId(const StringC &str,
					       const Location &loc,
					       size_t baseNumber,
					       Boolean isNdata,
					       const CharsetInfo &charset,
					       const StringC *lookupPublicId,
					       Messenger &mgr,
					       StringC &result) const
{
  return em_->expandSystemId(str,
			     (baseNumber ? base_[baseNumber - 1] : loc),
			     isNdata,
			     charset,
			     lookupPublicId,
			     mgr,
			     result);
}

Boolean SOEntityCatalog::lookup(const EntityDecl &entity,
				const Syntax &syntax,
				const CharsetInfo &charset,
				Messenger &mgr,
				StringC &result) const
{
  const CatalogEntry *entry = 0;
  const CatalogEntry *delegatedEntry = 0;
  if (entity.systemIdPointer())
    entry = systemIds_.lookup(*entity.systemIdPointer());
  if (entity.publicIdPointer()) {
    const CatalogEntry *publicEntry;
    Boolean delegated;
    publicEntry = findBestPublicEntry(*entity.publicIdPointer(),
				      entity.systemIdPointer() != 0,
				      charset,
				      delegated);
    if (publicEntry && delegated)
      delegatedEntry = publicEntry;
    // match for system id has priority over match for public id in same
    // catalog
    if (publicEntry
	&& (!entry || publicEntry->catalogNumber < entry->catalogNumber))
      entry = publicEntry;
  }
  if (entity.name().size() > 0
      && (!entry || entry->catalogNumber > 0)) {
    const CatalogEntry *entityEntry; 
    int tableIndex = (entity.declType() >= EntityDecl::parameterEntity
		      ? int(entity.declType()) - 1
		      : int(entity.declType()));
    StringC name(entity.name());
    Boolean subst;
    switch (entity.declType()) {
    case EntityDecl::parameterEntity:
      {
	StringC tem(name);
	name = syntax.peroDelim();
	name += tem;
      }
      // fall through
    case EntityDecl::generalEntity:
      subst = syntax.namecaseEntity();
      break;
    default:
      subst = syntax.namecaseGeneral();
      break;
    }
    if (!subst)
      entityEntry = names_[tableIndex].lookup(name,
					      entity.systemIdPointer() != 0);
    else
      entityEntry = names_[tableIndex].lookup(name,
					      syntax.upperSubstTable(),
					      entity.systemIdPointer() != 0);
    // match for public id has priority over match for entity in same
    // catalog
    if (entityEntry
	&& (!entry || entityEntry->catalogNumber < entry->catalogNumber))
      entry = entityEntry;
  }
  if (entry)
    return expandCatalogSystemId(entry->to,
				 entry->loc,
				 entry->baseNumber,
				 entity.dataType() == EntityDecl::ndata,
				 charset,
				 entry == delegatedEntry
				 ? entity.publicIdPointer()
				 : 0,
				 mgr,
				 result);
  if (entity.systemIdPointer())
    return em_->expandSystemId(*entity.systemIdPointer(),
			       entity.defLocation(),
			       entity.dataType() == EntityDecl::ndata,
			       charset,
			       0,
			       mgr,
			       result);
  return 0;
}

Boolean SOEntityCatalog::lookupPublic(const StringC &publicId,
				      const CharsetInfo &charset,
				      Messenger &mgr,
				      StringC &result) const
{
  Boolean delegated;
  const CatalogEntry *entry = findBestPublicEntry(publicId, 0, charset,
						  delegated);
  return (entry
	  && expandCatalogSystemId(entry->to, entry->loc, entry->baseNumber,
				   0, charset, delegated ? &publicId : 0,
				   mgr, result));
				 
}

Boolean SOEntityCatalog::lookupChar(const StringC &name,
				    const CharsetInfo &charset,
				    Messenger &mgr,
				    UnivChar &result) const
{
  Boolean delegated;
  const CatalogEntry *entry = findBestPublicEntry(name, 0, charset,
						  delegated);
  if (!entry)
    return 0;
  if (delegated)
    return 0;  // FIXME
  const StringC &number = entry->to;
  if (number.size() == 0)
    return 0;
  UnivChar n = 0;
  for (size_t i = 0; i < number.size(); i++) {
    int d = charset.digitWeight(number[i]);
    if (d < 0)
      return 0;
    if (n <= UnivChar(-1)/10 && (n *= 10) <= UnivChar(-1) - d)
      n += d;
  }
  result = n;
  return 1;
}

const CatalogEntry *
SOEntityCatalog::findBestPublicEntry(const StringC &publicId,
				     Boolean overrideOnly,
				     const CharsetInfo &charset,
				     Boolean &delegated) const
{
  Char slash = charset.execToDesc('/');
  Char colon = charset.execToDesc(':');
  const CatalogEntry *bestEntry = 0;
  for (size_t i = 0; i <= publicId.size(); i++) {
    if ((i + 1 < publicId.size()
	 && (publicId[i] == slash || publicId[i] == colon)
	 && publicId[i + 1] == publicId[i])
	|| (i >= 2
	    && (publicId[i - 1] == slash || publicId[i - 1] == colon)
	    && publicId[i - 2] == publicId[i - 1])) {
      StringC tem(publicId.data(), i);
      const CatalogEntry *entry = delegates_.lookup(tem, overrideOnly);
      if (entry
	  && (!bestEntry
	      || entry->catalogNumber <= bestEntry->catalogNumber)) {
	bestEntry = entry;
	delegated = 1;
      }
    }
  }
  const CatalogEntry *entry = publicIds_.lookup(publicId, overrideOnly);
  if (entry
      && (!bestEntry || entry->catalogNumber <= bestEntry->catalogNumber)) {
    bestEntry = entry;
    delegated = 0;
  }
  return bestEntry;
}

Boolean SOEntityCatalog::sgmlDecl(const CharsetInfo &charset,
				  Messenger &mgr,
				  StringC &result) const

{
  return haveSgmlDecl_ && expandCatalogSystemId(sgmlDecl_, sgmlDeclLoc_,
						sgmlDeclBaseNumber_,
						0, charset, 0, mgr, result);
}

Boolean SOEntityCatalog::document(const CharsetInfo &charset,
				  Messenger &mgr,
				  StringC &result) const

{
  return haveDocument_ && expandCatalogSystemId(document_, documentLoc_,
						documentBaseNumber_,
						0, charset, 0, mgr, result);
}

void SOEntityCatalog::addPublicId(StringC &publicId, StringC &systemId,
				  const Location &loc, Boolean override)
{
  CatalogEntry entry;
  entry.loc = loc;
  entry.catalogNumber = catalogNumber_;
  entry.baseNumber = haveCurrentBase_ ? base_.size() : 0;
  systemId.swap(entry.to);
  publicIds_.insert(publicId, entry, override);
}

void SOEntityCatalog::addDelegate(StringC &prefix, StringC &systemId,
				  const Location &loc, Boolean override)
{
  CatalogEntry entry;
  entry.loc = loc;
  entry.catalogNumber = catalogNumber_;
  entry.baseNumber = haveCurrentBase_ ? base_.size() : 0;
  systemId.swap(entry.to);
  delegates_.insert(prefix, entry, override);
}

void SOEntityCatalog::addSystemId(StringC &systemId, StringC &toSystemId,
				  const Location &loc)
{
  CatalogEntry entry;
  entry.loc = loc;
  entry.catalogNumber = catalogNumber_;
  entry.baseNumber = haveCurrentBase_ ? base_.size() : 0;
  toSystemId.swap(entry.to);
  systemIds_.insert(systemId, entry, false);
}

void SOEntityCatalog::addName(StringC &name, DeclType declType,
			      StringC &systemId, const Location &loc,
			      Boolean override)
{
  CatalogEntry entry;
  entry.loc = loc;
  entry.catalogNumber = catalogNumber_;
  entry.baseNumber = haveCurrentBase_ ? base_.size() : 0;
  int tableIndex = (declType >= EntityDecl::parameterEntity
		    ? int(declType) - 1
		    : int(declType));
  entry.serial = names_[tableIndex].count();
  systemId.swap(entry.to);
  names_[tableIndex].insert(name, entry, override);
}

void SOEntityCatalog::setSgmlDecl(StringC &str, const Location &loc)
{
  if (!haveSgmlDecl_) {
    haveSgmlDecl_ = true;
    str.swap(sgmlDecl_);
    sgmlDeclLoc_ = loc;
    sgmlDeclBaseNumber_ = haveCurrentBase_ ? base_.size() : 0;

  }
}

void SOEntityCatalog::setDocument(StringC &str, const Location &loc)
{
  if (!haveDocument_) {
    haveDocument_ = true;
    str.swap(document_);
    documentLoc_ = loc;
    documentBaseNumber_ = haveCurrentBase_ ? base_.size() : 0;
  }
}

void SOEntityCatalog::setBase(const Location &loc)
{
  if (loc.origin().isNull())
    haveCurrentBase_ = 0;
  else {
    haveCurrentBase_ = 1;
    base_.push_back(loc);
  }
}

SOEntityCatalog::Table::Table()
{
}

void SOEntityCatalog::Table::insert(const StringC &key,
				    const CatalogEntry &entry,
				    Boolean override)
{
  if (override)
    overrideEntries_.insert(key, entry, false);
  else {
    const CatalogEntry *e = overrideEntries_.lookup(key);
    if (!e)
      normalEntries_.insert(key, entry, false);
  }
}

const CatalogEntry *SOEntityCatalog::Table::lookup(const StringC &key,
						   Boolean overrideOnly) const
{
  if (!overrideOnly) {
    const CatalogEntry *e = normalEntries_.lookup(key);
    if (e)
      return e;
  }
  return overrideEntries_.lookup(key);
}

const CatalogEntry *
SOEntityCatalog::Table::lookup(const StringC &name,
			       const SubstTable<Char> &substTable,
			       Boolean overrideOnly) const
{
  HashTableIter<StringC,CatalogEntry> iter1(overrideEntries_);
  HashTableIter<StringC,CatalogEntry> iter2(normalEntries_);
  HashTableIter<StringC,CatalogEntry> *iters[2];
  int nIter = 0;
  iters[nIter++] = &iter1;
  if (!overrideOnly)
    iters[nIter++] = &iter2;
  const CatalogEntry *entry = 0;
  for (int i = 0; i < nIter; i++) {
    HashTableIter<StringC,CatalogEntry> &iter = *iters[i];
    const StringC *key;
    const CatalogEntry *value;
    StringC buffer;
    while (iter.next(key, value)) {
      buffer = *key;
      for (size_t j = 0; j < buffer.size(); j++)
	substTable.subst(buffer[j]);
      if (buffer == name) {
	if (!entry || value->serial < entry->serial)
	  entry = value;
      }
    }
  }
  return entry;
}

size_t SOEntityCatalog::Table::count() const
{
  return normalEntries_.count() + overrideEntries_.count();
}

CatalogParser::CatalogParser(const CharsetInfo &charset)
: categoryTable_(data),
  entityKey_(charset.execToDesc("ENTITY")),
  publicKey_(charset.execToDesc("PUBLIC")),
  systemKey_(charset.execToDesc("SYSTEM")),
  doctypeKey_(charset.execToDesc("DOCTYPE")),
  linktypeKey_(charset.execToDesc("LINKTYPE")),
  notationKey_(charset.execToDesc("NOTATION")),
  overrideKey_(charset.execToDesc("OVERRIDE")),
  sgmlDeclKey_(charset.execToDesc("SGMLDECL")),
  documentKey_(charset.execToDesc("DOCUMENT")),
  catalogKey_(charset.execToDesc("CATALOG")),
  yesKey_(charset.execToDesc("YES")),
  noKey_(charset.execToDesc("NO")),
  baseKey_(charset.execToDesc("BASE")),
  delegateKey_(charset.execToDesc("DELEGATE")),
  dtddeclKey_(charset.execToDesc("DTDDECL")),
  sgmlKey_(charset.execToDesc("SGML"))
{
  static const char lcletters[] = "abcdefghijklmnopqrstuvwxyz";
  static const char ucletters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  // minimum data other than lcletter, ucletter
  static const char minChars[] = "0123456789-.'()+,/:=?";
  static const char wwwMinChars[] = { 33, 35, 36, 37, 42, 59, 64, 95, 0 };
  static const char sChars[] = " \n\r\t";
  categoryTable_.setChar(0, nul);
  const char *p;
  const char *q;
  for (p = lcletters, q = ucletters; *p; p++, q++) {
    Char lc = charset.execToDesc(*p);
    Char uc = charset.execToDesc(*q);
    substTable_.addSubst(lc, uc);
    categoryTable_.setChar(lc, min);
    categoryTable_.setChar(uc, min);
  }
  for (p = sChars; *p; p++)
    categoryTable_.setChar(charset.execToDesc(*p), s);
  for (p = minChars; *p; p++)
    categoryTable_.setChar(charset.execToDesc(*p), min);
  for (p = wwwMinChars; *p; p++) {
    WideChar c;
    ISet<WideChar> set;
    if (charset.univToDesc(*p, c, set) > 0 && c <= Char(-1))
      categoryTable_.setChar(Char(c), min);
  }
  categoryTable_.setChar(charset.execToDesc('\''), lita);
  categoryTable_.setChar(charset.execToDesc('"'), lit);
  minus_ = charset.execToDesc('-');
  categoryTable_.setChar(minus_, minus);
  tab_ = charset.execToDesc('\t');
  re_ = charset.execToDesc('\r');
  rs_ = charset.execToDesc('\n');
  space_ = charset.execToDesc(' ');
  categoryTable_.setEe(eof);
}

void CatalogParser::parseCatalog(const StringC &sysid,
				 Boolean mustExist,
				 const CharsetInfo &sysidCharset,
				 const CharsetInfo &catalogCharset,
				 InputSourceOrigin *origin,
				 SOEntityCatalog *catalog,
				 Messenger &mgr)
{
  const Ptr<ExtendEntityManager> &em = catalog->entityManager();
  in_ = em->open(sysid, sysidCharset, origin,
		 mustExist ? 0 : ExtendEntityManager::mayNotExist, mgr);
  if (!in_)
    return;
  catalog_ = catalog;
  mgr_ = &mgr;
  override_ = 0;
  Boolean recovering = false;
  Vector<StringC> subSysids;
  Vector<Location> subSysidLocs;
  for (;;) {
    Param parm = parseParam();
    if (parm == nameParam) {
      upcase(param_);
      Boolean wasRecovering = recovering;
      recovering = false;
      if (param_ == publicKey_)
	parsePublic();
      else if (param_ == systemKey_)
	parseSystem();
      else if (param_ == entityKey_)
	parseNameMap(EntityDecl::generalEntity);
      else if (param_ == doctypeKey_)
	parseNameMap(EntityDecl::doctype);
      else if (param_ == linktypeKey_)
	parseNameMap(EntityDecl::linktype);
      else if (param_ == notationKey_)
	parseNameMap(EntityDecl::notation);
      else if (param_ == sgmlKey_)
	parseNameMap(EntityDecl::sgml);
      else if (param_ == sgmlDeclKey_) {
	if (parseArg())
	  catalog_->setSgmlDecl(param_, paramLoc_);
      }
      else if (param_ == documentKey_) {
	if (parseArg())
	  catalog_->setDocument(param_, paramLoc_);
      }
      else if (param_ == overrideKey_)
	parseOverride();
      else if (param_ == catalogKey_) {
	if (parseArg()) {
	  if (inLoop(paramLoc_))
	    break;
	  subSysids.resize(subSysids.size() + 1);
	  param_.swap(subSysids.back());
	  subSysidLocs.push_back(paramLoc_);
	}
      }
      else if (param_ == baseKey_) {
	if (parseArg()) {
	  StringC tem;
	  if (em->expandSystemId(param_,
				 paramLoc_,
				 0,
				 catalogCharset,
				 0,
				 mgr,
				 tem)) {
	    InputSource *in = em->open(tem,
				       catalogCharset,
				       InputSourceOrigin::make(paramLoc_),
				       0,
				       mgr);
	    if (in && (in->get(mgr) != InputSource::eE || !in->accessError()))
	      catalog->setBase(in->currentLocation());
	  }
	}
      }
      else if (param_ == delegateKey_)
	parseDelegate();
      else if (param_ == dtddeclKey_)
	parseDtddecl();
      else {
	if (!wasRecovering && parseParam() == eofParam)
	  break;
	recovering = true;
      }
    }
    else if (parm == eofParam)
      break;
    else if (!recovering) {
      recovering = true;
      message(CatalogMessages::nameExpected);
    }
  }
  delete in_;
  catalog->endCatalog();
  for (size_t i = 0; i < subSysids.size(); i++) {
    StringC tem;
    if (em->expandSystemId(subSysids[i], subSysidLocs[i], 0, catalogCharset,
			   0, mgr, tem))
      parseCatalog(tem, 1, catalogCharset, catalogCharset,
		   InputSourceOrigin::make(subSysidLocs[i]), catalog, mgr);
  }
}

Boolean CatalogParser::inLoop(const Location &loc)
{
  const InputSourceOrigin *origin = paramLoc_.origin()->asInputSourceOrigin();
  if (!origin)
    return 0;
  const ExternalInfo *info = origin->externalInfo();
  if (!info)
    return 0;
  StorageObjectLocation soLoc;
  if (!ExtendEntityManager::externalize(info,
					origin->startOffset(paramLoc_.index()),
					soLoc))
    return 0;
  for (;;) {
    const Location &parent = origin->parent();
    if (parent.origin().isNull())
      break;
    origin = parent.origin()->asInputSourceOrigin();
    if (!origin)
      break;
    const ExternalInfo *info1 = origin->externalInfo();
    if (info1) {
      StorageObjectLocation soLoc1;
      if (ExtendEntityManager::externalize(info1,
					   origin->startOffset(parent.index()),
					   soLoc1)) {
	if (soLoc.storageObjectSpec->storageManager
	    == soLoc1.storageObjectSpec->storageManager
	    && soLoc.actualStorageId == soLoc1.actualStorageId) {
	  setNextLocation(loc.origin()->parent());
	  message(CatalogMessages::inLoop);
	  return 1;
	}
      }
    }
  }
  return 0;
}

void CatalogParser::parseOverride()
{
  if (parseParam() != nameParam) {
    message(CatalogMessages::overrideYesOrNo);
    return;
  }
  upcase(param_);
  if (param_ == yesKey_)
    override_ = 1;
  else if (param_ == noKey_)
    override_ = 0;
  else
    message(CatalogMessages::overrideYesOrNo);
}

void CatalogParser::parsePublic()
{
  if (parseParam(minimumLiteral) != literalParam) {
    message(CatalogMessages::literalExpected);
    return;
  }
  StringC publicId;
  param_.swap(publicId);
  if (!parseArg())
    return;
  catalog_->addPublicId(publicId, param_, paramLoc_, override_);
}

void CatalogParser::parseDelegate()
{
  if (parseParam(minimumLiteral) != literalParam) {
    message(CatalogMessages::literalExpected);
    return;
  }
  StringC publicId;
  param_.swap(publicId);
  if (!parseArg())
    return;
  catalog_->addDelegate(publicId, param_, paramLoc_, override_);
}

void CatalogParser::parseDtddecl()
{
  message(CatalogMessages::dtddeclNotSupported);
  if (parseParam(minimumLiteral) != literalParam) {
    message(CatalogMessages::literalExpected);
    return;
  }
  if (!parseArg())
    return;
}

void CatalogParser::parseSystem()
{
  if (!parseArg())
    return;
  StringC systemId;
  param_.swap(systemId);
  Param parm = parseParam();
  if (parm == nameParam)
    message(CatalogMessages::systemShouldQuote);
  else if (parm != literalParam) {
    message(CatalogMessages::literalExpected);
    return;
  }
  catalog_->addSystemId(systemId, param_, paramLoc_);
}

void CatalogParser::parseNameMap(EntityDecl::DeclType declType)
{
  if (!parseArg())
    return;
  StringC name;
  param_.swap(name);
  if (!parseArg())
    return;
  catalog_->addName(name, declType, param_, paramLoc_, override_);
}

Boolean CatalogParser::parseArg()
{
  Param parm = parseParam();
  if (parm != nameParam && parm != literalParam) {
    message(CatalogMessages::nameOrLiteralExpected);
    return false;
  }
  return true;
}

CatalogParser::Param CatalogParser::parseParam(unsigned flags)
{
  for (;;) {
    Xchar c = get();
    switch (categoryTable_[c]) {
    case eof:
      return eofParam;
    case lit:
    case lita:
      parseLiteral(c, flags);
      return literalParam;
    case s:
      break;
    case nul:
      message(CatalogMessages::nulChar);
      break;
    case minus:
      c = get();
      if (c == minus_) {
	skipComment();
	break;
      }
      unget();
      // fall through
    default:
      parseName();
      return nameParam;
    }
  }
}

void CatalogParser::skipComment()
{
  for (;;) {
    Xchar c = get();
    if (c == minus_) {
      c = get();
      if (c == minus_)
	break;
    }
    if (c == InputSource::eE) {
      message(CatalogMessages::eofInComment);
      break;
    }
  }
}

void CatalogParser::parseLiteral(Char delim, unsigned flags)
{
  paramLoc_ = in_->currentLocation();
  enum { no, yesBegin, yesMiddle } skipping = yesBegin;
  param_.resize(0);
  for (;;) {
    Xchar c = get();
    if (c == InputSource::eE) {
      message(CatalogMessages::eofInLiteral);
      break;
    }
    if (Char(c) == delim)
      break;
    if (flags & minimumLiteral) {
      if (!isMinimumData(c))
	message(CatalogMessages::minimumData);
      if (c == rs_)
	;
      else if (c == space_ || c == re_) {
	if (skipping == no) {
	  param_ += space_;
	  skipping = yesMiddle;
	}
      }
      else {
	skipping = no;
	param_ += Char(c);
      }
    }
    else
      param_ += Char(c);
  }
  if (skipping == yesMiddle)
    param_.resize(param_.size() - 1);
}

void CatalogParser::parseName()
{
  paramLoc_ = in_->currentLocation();
  size_t length;
  for (length = 1;; length++) {
    Xchar c = in_->tokenChar(messenger());
    int cat = categoryTable_[c];
    if (cat == eof || cat == s)
      break;
    // FIXME maybe check for LIT or LITA
    if (cat == nul)
      message(CatalogMessages::nulChar);
  }
  in_->endToken(length);
  param_.assign(in_->currentTokenStart(), in_->currentTokenLength());
}

void CatalogParser::upcase(StringC &str)
{
  for (size_t i = 0; i < str.size(); i++)
    substTable_.subst(str[i]);
}

void CatalogParser::dispatchMessage(const Message &msg)
{
  mgr_->dispatchMessage(msg);
}

void CatalogParser::dispatchMessage(Message &msg)
{
  mgr_->dispatchMessage(msg);
}

void CatalogParser::initMessage(Message &msg)
{
  msg.loc = in_->currentLocation();
}

#ifdef SP_NAMESPACE
}
#endif
