/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: NPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is mozilla.org code.
 *
 * The Initial Developer of the Original Code is 
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Pierre Phaneuf <pp@ludusdesign.com>
 *
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the NPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the NPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
       
  
#include "nsDTDUtils.h" 
#include "CNavDTD.h" 
#include "nsIParserNode.h" 
#include "nsParserNode.h" 
#include "nsIChannel.h"
#include "nsIServiceManager.h"
#include "nsUnicharUtils.h"

MOZ_DECL_CTOR_COUNTER(nsEntryStack)
MOZ_DECL_CTOR_COUNTER(nsDTDContext)
MOZ_DECL_CTOR_COUNTER(nsTokenAllocator)
MOZ_DECL_CTOR_COUNTER(CNodeRecycler)
 
/**************************************************************************************
  A few notes about how residual style handling is performed:
   
    1. The style stack contains nsTagEntry elements. 
    2. Every tag on the containment stack can have it's own residual style stack.
    3. When a style leaks, it's mParent member is set to the level on the stack where 
       it originated. A node with an mParent of 0 is not opened on tag stack, 
       but is open on stylestack.
    4. An easy way to tell that a container on the element stack is a residual style tag
       is that it's use count is >1.

 **************************************************************************************/


/**
 * Default constructor
 * @update	harishd 04/04/99
 * @update  gess 04/22/99
 */
nsEntryStack::nsEntryStack()  {

  MOZ_COUNT_CTOR(nsEntryStack);
  
  mCapacity=0;
  mCount=0;
  mEntries=0;
}

/**
 * Default destructor
 * @update  harishd 04/04/99
 * @update  gess 04/22/99
 */
nsEntryStack::~nsEntryStack() {

  MOZ_COUNT_DTOR(nsEntryStack);

  if(mEntries) {
    //add code here to recycle the node if you have one...  
    delete [] mEntries;
    mEntries=0;
  }

  mCount=mCapacity=0;
}

/**
 * Release all objects in the entry stack
 */
void 
nsEntryStack::ReleaseAll(nsNodeAllocator* aNodeAllocator)
{
  NS_WARN_IF_FALSE(aNodeAllocator,"no allocator? - potential leak!");

  if(aNodeAllocator) {
    NS_WARN_IF_FALSE(mCount >= 0,"count should not be negative");
    while(mCount > 0) {
      nsCParserNode* node=this->Pop();
      IF_FREE(node,aNodeAllocator);
    }
  }
}

/**
 * Resets state of stack to be empty.
 * @update harishd 04/04/99
 */
void nsEntryStack::Empty(void) {
  mCount=0;
}


/**
 * 
 * @update  gess 04/22/99
 */
void nsEntryStack::EnsureCapacityFor(PRInt32 aNewMax,PRInt32 aShiftOffset) {
  if(mCapacity<aNewMax){ 

    const int kDelta=16;

    PRInt32 theSize = kDelta * ((aNewMax / kDelta) + 1);
    nsTagEntry* temp=new nsTagEntry[theSize]; 
    mCapacity=theSize;

    if(temp){ 
      PRInt32 index=0; 
      for(index=0;index<mCount;index++) {
        temp[aShiftOffset+index]=mEntries[index];
      }
      if(mEntries) delete [] mEntries;
      mEntries=temp;
    }
    else{
      //XXX HACK! This is very bad! We failed to get memory.
    }
  } //if
}

/**
 * 
 * @update  gess 04/22/99
 */
void nsEntryStack::Push(const nsCParserNode* aNode,nsEntryStack* aStyleStack) {
  if(aNode) {

    EnsureCapacityFor(mCount+1);

    ((nsCParserNode*)aNode)->mUseCount++;

    mEntries[mCount].mTag=(eHTMLTags)aNode->GetNodeType();
    mEntries[mCount].mNode=NS_CONST_CAST(nsCParserNode*,aNode);
    
    IF_HOLD(mEntries[mCount].mNode);
    
    mEntries[mCount].mParent=aStyleStack;
    mEntries[mCount++].mStyles=0;
  }
}


/**
 * This method inserts the given node onto the front of this stack
 *
 * @update  gess 11/10/99
 */
void nsEntryStack::PushFront(const nsCParserNode* aNode,nsEntryStack* aStyleStack) {
  if(aNode) {

    if(mCount<mCapacity) {
      PRInt32 index=0; 
      for(index=mCount;index>0;index--) {
        mEntries[index]=mEntries[index-1];
      }
    }
    else EnsureCapacityFor(mCount+1,1);


    ((nsCParserNode*)aNode)->mUseCount++;

    mEntries[0].mTag=(eHTMLTags)aNode->GetNodeType();
    mEntries[0].mNode=NS_CONST_CAST(nsCParserNode*,aNode);
    
    IF_HOLD(mEntries[0].mNode);
    
    mEntries[0].mParent=aStyleStack;
    mEntries[0].mStyles=0;
    mCount++;
  }
}

/**
 * 
 * @update  gess 11/10/99
 */
void nsEntryStack::Append(nsEntryStack *aStack) {
  if(aStack) {

    PRInt32 theCount=aStack->mCount;

    EnsureCapacityFor(mCount+aStack->mCount,0);

    PRInt32 theIndex=0;
    for(theIndex=0;theIndex<theCount;theIndex++){
      mEntries[mCount]=aStack->mEntries[theIndex];
      mEntries[mCount++].mParent=0;
    }
  }
} 
 
/**
 * This method removes the node for the given tag
 * from anywhere within this entry stack, and shifts
 * other entries down.
 * 
 * NOTE: It's odd to be removing an element from the middle
 *       of a stack, but it's necessary because of how MALFORMED
 *       html can be. 
 * 
 * anIndex: the index within the stack of the tag to be removed
 * aTag: the id of the tag to be removed
 * @update  gess 02/25/00
 */
nsCParserNode* nsEntryStack::Remove(PRInt32 anIndex,eHTMLTags aTag) {
  nsCParserNode* result=0;

  if((0<mCount) && (anIndex<mCount)){
    result=mEntries[anIndex].mNode;

    ((nsCParserNode*)result)->mUseCount--;

    PRInt32 theIndex=0;
    mCount-=1;
    for(theIndex=anIndex;theIndex<mCount;theIndex++){
      mEntries[theIndex]=mEntries[theIndex+1];
    }

    mEntries[mCount].mNode=0;
    mEntries[mCount].mStyles=0;

    nsEntryStack* theStyleStack=mEntries[anIndex].mParent;

    if(theStyleStack) {
      //now we have to tell the residual style stack where this tag
      //originated that it's no longer in use.
      PRUint32 scount=theStyleStack->mCount;
      PRUint32 sindex=0;

      nsTagEntry *theStyleEntry=theStyleStack->mEntries;
      for(sindex=scount-1;sindex>0;sindex--){            
        if(theStyleEntry->mTag==aTag) {
          theStyleEntry->mParent=0;  //this tells us that the style is not open at any level
          break;
        }
        theStyleEntry++;
      } //for
    }
  }
  return result;
}

/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
nsCParserNode* nsEntryStack::Pop(void) {

  nsCParserNode* result=0;

  if(0<mCount) {
    result=mEntries[--mCount].mNode;

    ((nsCParserNode*)result)->mUseCount--;

    mEntries[mCount].mNode=0;
    mEntries[mCount].mStyles=0;

    nsEntryStack* theStyleStack=mEntries[mCount].mParent;

    if(theStyleStack) {
      //now we have to tell the residual style stack where this tag
      //originated that it's no longer in use.
      PRUint32 scount=theStyleStack->mCount;
      PRUint32 sindex=0;

      nsTagEntry *theStyleEntry=theStyleStack->mEntries;
      for(sindex=scount-1;sindex>0;sindex--){            
        if(theStyleEntry->mTag==mEntries[mCount].mTag) {
          theStyleEntry->mParent=0;  //this tells us that the style is not open at any level
          break;
        }
        theStyleEntry++;
      } //for
    }
  }
  return result;
} 

/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
eHTMLTags nsEntryStack::First() const {
  eHTMLTags result=eHTMLTag_unknown;
  if(0<mCount){
    result=mEntries[0].mTag;
  }
  return result;
}

/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
nsCParserNode* nsEntryStack::NodeAt(PRInt32 anIndex) const {
  nsCParserNode* result=0;
  if((0<mCount) && (anIndex<mCount)) {
    result=mEntries[anIndex].mNode;
  }
  return result;
}

/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
eHTMLTags nsEntryStack::TagAt(PRInt32 anIndex) const {
  eHTMLTags result=eHTMLTag_unknown;
  if((0<mCount) && (anIndex<mCount)) {
    result=mEntries[anIndex].mTag;
  }
  return result;
}

/**
 * 
 * @update  gess 04/21/99
 */
nsTagEntry* nsEntryStack::EntryAt(PRInt32 anIndex) const {
  nsTagEntry *result=0;
  if((0<mCount) && (anIndex<mCount)) {
    result=&mEntries[anIndex];
  }
  return result;
}


/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
eHTMLTags nsEntryStack::operator[](PRInt32 anIndex) const {
  eHTMLTags result=eHTMLTag_unknown;
  if((0<mCount) && (anIndex<mCount)) {
    result=mEntries[anIndex].mTag;
  }
  return result;
}


/**
 * 
 * @update  harishd 04/04/99
 * @update  gess 04/21/99
 */
eHTMLTags nsEntryStack::Last() const {
  eHTMLTags result=eHTMLTag_unknown;
  if(0<mCount) {
    result=mEntries[mCount-1].mTag;
  }
  return result;
}

/***************************************************************
  Now define the dtdcontext class
 ***************************************************************/


/**
 * 
 * @update	gess 04.21.2000
 */
nsDTDContext::nsDTDContext() : mStack(), mEntities(0){

  MOZ_COUNT_CTOR(nsDTDContext);
  mResidualStyleCount=0;
  mContextTopIndex=-1;
  mTableStates=0;
  mTokenAllocator=0;
  mNodeAllocator=0;
  mAllBits=0;

#ifdef DEBUG
  memset(mXTags,0,sizeof(mXTags));
  mCounters = 0;
#endif
} 
 
#ifdef DEBUG
  class CEntityDeallocator: public nsDequeFunctor{
  public:
    virtual void* operator()(void* anObject) {
      CNamedEntity *theEntity=(CNamedEntity*)anObject;
      delete theEntity;
      return 0;
    }
  };
#endif

/**
 * 
 * @update	gess9/10/98
 */
nsDTDContext::~nsDTDContext() {
  MOZ_COUNT_DTOR(nsDTDContext);

  while(mTableStates) {
    //pop the current state and restore it's predecessor, if any...
    CTableState *theState=mTableStates;      
    mTableStates=theState->mPrevious;
    delete theState;
  } 

#ifdef DEBUG
  CEntityDeallocator theDeallocator;
  mEntities.ForEach(theDeallocator);
  if(mCounters) {
    delete [] mCounters;
    mCounters = 0;
  }
#endif

}

#ifdef DEBUG

CNamedEntity* nsDTDContext::GetEntity(const nsAString& aName)const {
  if (aName.Length() > 2) {
    nsAString::const_iterator start, end;
    aName.BeginReading(start);
    aName.EndReading(end);
    

    PRInt32 theLen=aName.Length();
    PRUnichar theChar=aName.Last();

    // skip past leading and trailing quotes/etc
    if(kQuote==*start) {
      start++;
    }
    
    if(kSemicolon==theChar ||
       kQuote == theChar) {
      end--;
    }
    
  
    const nsAString& entityName = Substring(start, end);
    
    PRInt32 theCount=mEntities.GetSize();
    PRInt32 theIndex=0;
    for(theIndex=0;theIndex<theCount;theIndex++) {
      CNamedEntity *theResult=(CNamedEntity*)mEntities.ObjectAt(theIndex);
      if(theResult &&
         theResult->mName.Equals(entityName,
                                 nsCaseInsensitiveStringComparator())) {
        return theResult;
      }
    }
  }
  return 0;
}

CNamedEntity*  nsDTDContext::RegisterEntity(const nsAString& aName,const nsAString& aValue) {
  CNamedEntity *theEntity=GetEntity(aName);
  if(!GetEntity(aName)){
    theEntity=new CNamedEntity(aName,aValue);
    mEntities.Push(theEntity);
  }
  return theEntity;
}

/****************************************************************
  The abacus class is useful today for debug purposes, but it
  will eventually serve as the implementation for css counters.

  This implementation is fine for static documents, but woefully
  inadequate for dynamic documents. (This about what happens if
  someone inserts a new counter using the DOM? -- The other
  numbers in that "group" should be renumbered.)

  In order to be dynamic, we need a counter "group" manager that
  is aware of layout (geometry at least) -- and that has a 
  mechanism for notifying markers that need to be updated, along
  with the ability to cause incremental reflow to occur in a
  localized context (so the counters display correctly).

 ****************************************************************/

class CAbacus {
public:

  enum eNumFormat {eUnknown,eAlpha,eDecimal,eRoman,eSpelled,eHex,eBinary,eFootnote,eUserSeries};

  CAbacus(PRInt32 aDefaultValue=0,eNumFormat aFormat=eDecimal) {
    mUserSeries=0;
    mFormat=aFormat;
    mCase=PR_FALSE;
    mValue=0;
    mUserBase=0;
  }

  ~CAbacus() {
  }

  void  SetValue(int aStartValue) {mValue=aStartValue;}
  void  SetNumberingStyle(eNumFormat aFormat) {mFormat=aFormat;}
  void  SetUserSeries(const char* aSeries,int aUserBase) {mUserSeries=aSeries; mUserBase=aUserBase;}
  void  SetCase(PRBool alwaysUpper) {mCase=alwaysUpper;}

  void  GetNextValueAsString(nsString& aString) {
    GetFormattedString(mFormat,mValue++,aString,mUserSeries,0,mUserBase);
  }

  void  GetValueAsString(nsString& aString) {
    GetFormattedString(mFormat,mValue,aString,mUserSeries,0,mUserBase);
  }


  /**
   * Get a counter string in the given style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aFormat -- format of choice
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void  GetFormattedString(eNumFormat aFormat,PRInt32 aValue, nsString& aString,const char* aCharSet, int anOffset, int aBase) {
	  switch (aFormat) {
		  case	eDecimal:	    DecimalString(aValue,aString);  break;
		  case	eHex:			    HexString(aValue,aString);      break;
		  case	eBinary:		  BinaryString(aValue,aString);   break;
		  case	eAlpha:		    AlphaString(aValue,aString);    break;
		  case	eSpelled:		  SpelledString(aValue,aString);  break;
		  case	eRoman:		    RomanString(aValue,aString);    break;
		  case	eFootnote:	  FootnoteString(aValue,aString); break;
		  case	eUserSeries:	SeriesString(aValue,aString,aCharSet,anOffset,aBase); break;
      default:
        DecimalString(aValue,aString);  break;
	  }
  }

  /**
   * Compute a counter string in the casted-series style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void  SeriesString(PRInt32 aValue,nsString& aString,const char* aCharSet, int offset, int base) {
	  int	ndex=0;
	  int	root=1;
	  int	next=base;
	  int	expn=1;

    aString.Truncate();
    if(aValue<0)
      aString.Append(PRUnichar('-'));

	  aValue=abs(aValue);	  // must be positive here...
    while(next<=aValue)	{	// scale up in baseN; exceed current value.
		  root=next;
		  next*=base;
		  expn++;
	  }
		    
	  while(expn--) {
		  ndex = ((root<=aValue) && (root)) ? (aValue/root): 0;
		  aValue%=root;
      aString.Append(PRUnichar(aCharSet[ndex+((root>1)*offset)]));
		  root/=base;
    }
  }

  /**
   * Compute a counter string in the spelled style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void SpelledString(PRInt32 aValue,nsString& aString) {

    static	char	ones[][12]=   {"zero","one ","two ","three ","four ","five ","six ","seven ","eight ","nine ","ten "};
    static	char	teens[][12]=	{"ten ","eleven ","twelve ","thirteen ","fourteen ","fifteen ","sixteen ","seventeen ","eighteen ","nineteen "};
    static	char	tens[][12]=   {"","ten ","twenty ","thirty ","fourty ","fifty ","sixty ","seventy ","eighty ","ninety ","hundred "};
    static	char	bases[][20]=  {"","hundred ","thousand ","million ","billion ","trillion ","quadrillion ","quintillion ","bajillion "};

    aString.Truncate();
    if(aValue<0)
      aString.Append(PRUnichar('-'));

	  PRInt32	root=1000000000;
	  PRInt32	expn=4;
	  PRInt32	modu=0;
    
	  aValue=abs(aValue);
    if(0<aValue) {      

	    while(root && aValue) {		
        PRInt32 temp=aValue/root;
		    if(temp) {
          PRInt32 theDiv=temp/100;
          if (theDiv) {//start with hundreds part
            aString.AppendWithConversion(ones[theDiv]);
            aString.AppendWithConversion(bases[1]);
		      }
			    modu=(temp%10);
          theDiv=(temp%100)/10;
          if (theDiv) {
				    if (theDiv<2) {
              aString.AppendWithConversion(teens[modu]);
						  modu=0;
					  }
            else aString.AppendWithConversion(tens[theDiv]);
          }
			    if (modu) 
            aString.AppendWithConversion(ones[modu]); //do remainder
			    aValue-=(temp*root);
			    if (expn>1)	
            aString.AppendWithConversion(bases[expn]);
		    }
		    expn--;
		    root/=1000;
	    }
    }
    else aString.AppendWithConversion(ones[0]);
  }

  /**
   * Compute a counter string in the decimal format for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void DecimalString(PRInt32 aValue,nsString& aString) {
    aString.Truncate();
    aString.AppendInt(aValue);
  }

  /**
   * Compute a counter string in binary format for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void BinaryString(PRInt32 aValue,nsString& aString) {
	  static char	kBinarySet[]="01";

	  if (aValue<0)
		  aValue=65536-abs(aValue);
    SeriesString(aValue,aString,kBinarySet,0,2);
  }

  /**
   * Compute a counter string in hex format for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void HexString(PRInt32 aValue,nsString& aString) {
    static char	kHexSet[]="0123456789ABCDEF";

	  if (aValue<0)
		  aValue=65536-abs(aValue);
    SeriesString(aValue,aString,kHexSet,0,16);
  }

  /**
   * Compute a counter string in the roman style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void RomanString(PRInt32 aValue,nsString& aString) {
	  static PRUnichar digitsA[] = { PRUnichar('i'),
                                   PRUnichar('x'),
                                   PRUnichar('c'),
                                   PRUnichar('m') };
	  static PRUnichar digitsB[] = { PRUnichar('v'),
                                   PRUnichar('l'),
                                   PRUnichar('d'),
                                   PRUnichar('?') };

    aString.Truncate();
    if(aValue<0)
      aString.Append(PRUnichar('-'));

    aValue=abs(aValue);
	  char	decStr[20];
	  sprintf(decStr,"%d", aValue);

	  int len=strlen(decStr);
	  int romanPos=len;
    int digitPos=0;
    int n=0;

	  for(digitPos=0;digitPos<len;digitPos++) {
		  romanPos--;
		  switch(decStr[digitPos]) {
			  case	'0':	break;
			  case	'3':	aString.Append(digitsA[romanPos]);
			  case	'2':	aString.Append(digitsA[romanPos]);
			  case	'1':	aString.Append(digitsA[romanPos]);
				  break;
			  case	'4':	aString.Append(digitsA[romanPos]);
			  case 	'5':	case	'6':
			  case	'7':	case 	'8':	
          aString.Append(digitsB[romanPos]);
					for(n=0;n<(decStr[digitPos]-'5');n++)
            aString.Append(digitsA[romanPos]);
				  break;
			  case	'9':	          
          aString.Append(digitsA[romanPos]);
          aString.Append(digitsA[romanPos]);
					break;
		  }
	  }
  }

  /**
   * Compute a counter string in the alpha style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void AlphaString(PRInt32 aValue,nsString& aString) {
	  static const char kAlphaSet[]="abcdefghijklmnopqrstuvwxyz";
	  
	  if (0<aValue)
      SeriesString(aValue-1,aString,kAlphaSet,-1,26);    
  }

  /**
   * Compute a counter string in the footnote style for the given value.
   * 
   * @update	rickg 6June2000
   * 
   * @param   aValue  -- cardinal value of string
   * @param   aString -- will hold result
   */
  static void FootnoteString(PRInt32 aValue,nsString& aString) {
    static char	kFootnoteSet[]="abcdefg";

	  int			seriesLen = strlen (kFootnoteSet) - 1;
	  int			count=0;
    int     repCount=0;
	  int			modChar=0;
	  
    aString.Truncate();

    aValue=abs(aValue);
	  repCount=((aValue-1)/seriesLen);
	  modChar=aValue-(repCount*seriesLen);
	  
    for(count=0;count<=repCount;count++) {
      aString.Append(PRUnichar(kFootnoteSet[modChar]));
    }
  }

protected:

  const char* mUserSeries;
  eNumFormat  mFormat;
  PRBool      mCase;
  PRInt32     mValue;
  int         mUserBase;
};


/**
 * 
 * @update	gess 11May2000
 */
void nsDTDContext::AllocateCounters(void) {
  if(!mCounters) {
    mCounters = new PRInt32 [NS_HTML_TAG_MAX]; //in addition to reseting, you may need to allocate.
    ResetCounters();
  }
}

/**
 * 
 * @update	gess 11May2000
 */
void nsDTDContext::ResetCounters(void) {
  if(mCounters) {
    memset(mCounters,0,NS_HTML_TAG_MAX*sizeof(PRInt32));
  }
}

/**********************************************************
  @update: rickg  17May2000

  Call this to handle counter attributes:
    name="group"
    value="nnn"
    noincr="?"
    format="alpha|dec|footnote|hex|roman|spelled|talk"

  returns the newly incremented value for the (determined) group.
 **********************************************************/
PRInt32 nsDTDContext::IncrementCounter(eHTMLTags aTag,nsIParserNode& aNode,nsString& aResult) {

  PRInt32     result=0;

  PRInt32       theIndex=0;
  PRInt32       theNewValue=-1; //-1 is interpreted to mean "don't reset the counter sequence.
  PRInt32       theIncrValue=1; //this may get set to 0 if we see a "noincr" key.
  PRInt32       theCount=aNode.GetAttributeCount();
  CNamedEntity *theEntity=0;

  CAbacus::eNumFormat  theNumFormat=CAbacus::eDecimal;

  for(theIndex=0;theIndex<theCount;theIndex++){
    const nsAString& theKey=aNode.GetKeyAt(theIndex);
    const nsAString& theValue=aNode.GetValueAt(theIndex);

    if(theKey.Equals(NS_LITERAL_STRING("name"), nsCaseInsensitiveStringComparator())){
      theEntity=GetEntity(theValue);
      if(!theEntity) {
        theEntity=RegisterEntity(theValue,theValue);
        theEntity->mOrdinal=0;
      }
      aTag=eHTMLTag_userdefined;
    }
    else if(theKey.Equals(NS_LITERAL_STRING("noincr"), nsCaseInsensitiveStringComparator())){
      theIncrValue=0;
    }
    else if(theKey.Equals(NS_LITERAL_STRING("format"), nsCaseInsensitiveStringComparator())){
      nsAString::const_iterator start;
      
      PRUnichar theChar=*theValue.BeginReading(start);
      if('"'==theChar)
        theChar=*(++start);
      switch(theChar){
        case 'A': case 'a': theNumFormat=CAbacus::eAlpha;   break;
        case 'B': case 'b': theNumFormat=CAbacus::eBinary;  break;
        case 'D': case 'd': theNumFormat=CAbacus::eDecimal; break;
        case 'H': case 'h': theNumFormat=CAbacus::eHex;     break;
        case 'R': case 'r': theNumFormat=CAbacus::eRoman;   break;
        case 'S': case 's': theNumFormat=CAbacus::eSpelled; break;
        default:
          theNumFormat=CAbacus::eDecimal;
          break;
      }
      //determine numbering style
    }
    else if(theKey.Equals(NS_LITERAL_STRING("value"), nsCaseInsensitiveStringComparator())){
      PRInt32 err=0;
      theNewValue=atoi(NS_LossyConvertUCS2toASCII(theValue).get());
      if(!err) {

        theIncrValue=0;

        AllocateCounters();
        if(mCounters) {
          mCounters[aTag]=theNewValue;
        }
      }
      else theNewValue=-1;
    }
  }

  if(theEntity && (eHTMLTag_userdefined==aTag)) {
    result=theEntity->mOrdinal+=theIncrValue;
  }
  else {
    AllocateCounters();
    if(mCounters) {
      result=mCounters[aTag]+=theIncrValue;
    }
    else result=0;
  }
  CAbacus::GetFormattedString(theNumFormat,result,aResult,0,0,0);

  return result;
}

#endif

/**
 * 
 * @update  gess7/9/98
 */
PRBool nsDTDContext::HasOpenContainer(eHTMLTags aTag) const {
  PRInt32 theIndex=mStack.LastOf(aTag);
  return PRBool(-1<theIndex);
}

/**
 * 
 * @update  gess7/9/98
 */
void nsDTDContext::Push(const nsCParserNode* aNode,nsEntryStack* aStyleStack) {
  if(aNode) {

#ifdef  NS_DEBUG
    eHTMLTags theTag=(eHTMLTags)aNode->GetNodeType();
    int size=mStack.mCount;
    if(size< eMaxTags)
      mXTags[size]=theTag;
#endif
    mStack.Push(aNode,aStyleStack);
  }
}

/** 
 * @update  gess 11/11/99, 
 *          harishd 04/04/99
 */
nsCParserNode* nsDTDContext::Pop(nsEntryStack *&aChildStyleStack) {

  PRInt32         theSize=mStack.mCount;
  nsCParserNode*  result=0;

  if(0<theSize) {

#ifdef  NS_DEBUG
    if ((theSize>0) && (theSize <= eMaxTags))
      mXTags[theSize-1]=eHTMLTag_unknown;
#endif


    nsTagEntry* theEntry=mStack.EntryAt(mStack.mCount-1);
    if(theEntry) {
      aChildStyleStack=theEntry->mStyles;
    }

    result=mStack.Pop();
    theEntry->mParent=0;
  }

  return result;
}
 
/**
 * 
 * @update  harishd 04/07/00
 */

nsCParserNode* nsDTDContext::Pop() {
  nsEntryStack   *theTempStyleStack=0; // This has no use here...
  return Pop(theTempStyleStack);
}

/**
 * 
 * @update  gess7/9/98
 */
eHTMLTags nsDTDContext::First(void) const {
  return mStack.First();
}

/**
 * 
 * @update  gess7/9/98
 */
eHTMLTags nsDTDContext::TagAt(PRInt32 anIndex) const {
  return mStack.TagAt(anIndex);
}


/**
 * 
 * @update  gess7/9/98
 */
nsTagEntry* nsDTDContext::LastEntry(void) const {
  return mStack.EntryAt(mStack.mCount-1);
}

/**
 *  
 * @update  gess7/9/98
 */
eHTMLTags nsDTDContext::Last() const {
  return mStack.Last();
}


/**
 * 
 * @update  gess7/9/98
 */
nsEntryStack* nsDTDContext::GetStylesAt(PRInt32 anIndex) const {
  nsEntryStack* result=0;

  if(anIndex<mStack.mCount){
    nsTagEntry* theEntry=mStack.EntryAt(anIndex);
    if(theEntry) {
      result=theEntry->mStyles;
    }
  }
  return result;
}


/**
 * 
 * @update  gess 04/28/99
 */
void nsDTDContext::PushStyle(const nsCParserNode* aNode){

  nsTagEntry* theEntry=mStack.EntryAt(mStack.mCount-1);
  if(theEntry ) {
    nsEntryStack* theStack=theEntry->mStyles;
    if(!theStack) {
      theStack=theEntry->mStyles=new nsEntryStack();
    }
    if(theStack) {
      theStack->Push(aNode);
      mResidualStyleCount++;
    }
  } //if
}


/**
 * Call this when you have an EntryStack full of styles
 * that you want to push at this level.
 * 
 * @update  gess 04/28/99
 */
void nsDTDContext::PushStyles(nsEntryStack *aStyles){

  if(aStyles) {
    nsTagEntry* theEntry=mStack.EntryAt(mStack.mCount-1);
    if(theEntry ) {
      nsEntryStack* theStyles=theEntry->mStyles;
      if(!theStyles) {
        theEntry->mStyles=aStyles;

        PRUint32 scount=aStyles->mCount;
        PRUint32 sindex=0;

        theEntry=aStyles->mEntries;
        for(sindex=0;sindex<scount;sindex++){            
          theEntry->mParent=0;  //this tells us that the style is not open at any level
          theEntry++;
          mResidualStyleCount++;
        } //for

      }
      else {
        theStyles->Append(aStyles);
        //  Delete aStyles since it has been copied to theStyles...
        delete aStyles;
        aStyles=0;
      }
    } //if(theEntry )
    else if(mStack.mCount==0) {
      // If you're here it means that we have hit the rock bottom
      // ,of the stack, and there's no need to handle anymore styles.
      // Fix for bug 29048
      IF_DELETE(aStyles,mNodeAllocator);
    }
  }//if(aStyles)
}


/** 
 * 
 * @update  gess 04/28/99
 */
nsCParserNode* nsDTDContext::PopStyle(void){
  nsCParserNode *result=0;

  nsTagEntry *theEntry=mStack.EntryAt(mStack.mCount-1);
  if(theEntry && (theEntry->mNode)) {
    nsEntryStack* theStyleStack=theEntry->mParent;
    if(theStyleStack){
      result=theStyleStack->Pop();
      mResidualStyleCount--;
    }
  } //if
  return result;
}

/** 
 * 
 * @update  gess 04/28/99
 */
nsCParserNode* nsDTDContext::PopStyle(eHTMLTags aTag){

  PRInt32 theLevel=0;
  nsCParserNode* result=0;

  for(theLevel=mStack.mCount-1;theLevel>0;theLevel--) {
    nsEntryStack *theStack=mStack.mEntries[theLevel].mStyles;
    if(theStack) {
      if(aTag==theStack->Last()) {
        result=theStack->Pop();
        mResidualStyleCount--;
        break; // Fix bug 50710 - Stop after finding a style.
      } else {
        // NS_ERROR("bad residual style entry");
      }
    } 
  }

  return result;
}

/** 
 * 
 * This is similar to popstyle, except that it removes the
 * style tag given from anywhere in the style stack, and
 * not just at the top.
 *
 * @update  gess 01/26/00
 */
void nsDTDContext::RemoveStyle(eHTMLTags aTag){
  
  PRInt32 theLevel=mStack.mCount;
  
  while (theLevel) {
    nsEntryStack *theStack=GetStylesAt(--theLevel);
    if (theStack) {
      PRInt32 index=theStack->mCount;
      while (index){
        nsTagEntry *theEntry=theStack->EntryAt(--index);
        if (aTag==(eHTMLTags)theEntry->mNode->GetNodeType()) {
          mResidualStyleCount--;
          nsCParserNode* result=theStack->Remove(index,aTag);
          IF_FREE(result, mNodeAllocator);  
          return;
        } 
      }
    } 
  }
}

/**
 * This gets called when the parser module is getting unloaded
 * 
 * @return  nada
 */
void nsDTDContext::ReleaseGlobalObjects(){
}


/**************************************************************
  Now define the nsTokenAllocator class...
 **************************************************************/

static const size_t  kTokenBuckets[]       ={sizeof(CStartToken),sizeof(CAttributeToken),sizeof(CCommentToken),sizeof(CEndToken)};
static const PRInt32 kNumTokenBuckets      = sizeof(kTokenBuckets) / sizeof(size_t);
static const PRInt32 kInitialTokenPoolSize = NS_SIZE_IN_HEAP(sizeof(CToken)) * 200;

/**
 * 
 * @update  gess7/25/98
 * @param 
 */
nsTokenAllocator::nsTokenAllocator() {

  MOZ_COUNT_CTOR(nsTokenAllocator);

  mArenaPool.Init("TokenPool", kTokenBuckets, kNumTokenBuckets, kInitialTokenPoolSize);

#ifdef NS_DEBUG
  int i=0;
  for(i=0;i<eToken_last-1;i++) {
    mTotals[i]=0;
  }
#endif

}

/**
 * Destructor for the token factory
 * @update  gess7/25/98
 */
nsTokenAllocator::~nsTokenAllocator() {

  MOZ_COUNT_DTOR(nsTokenAllocator);

}

class CTokenFinder: public nsDequeFunctor{
public:
  CTokenFinder(CToken* aToken) {mToken=aToken;}
  virtual void* operator()(void* anObject) {
    if(anObject==mToken) {
      return anObject;
    }
    return 0;
  }
  CToken* mToken;
};

/**
 * Let's get this code ready to be reused by all the contexts.
 * 
 * @update	rickg 12June2000
 * @param   aType -- tells you the type of token to create
 * @param   aTag  -- tells you the type of tag to init with this token
 * @param   aString -- gives a default string value for the token
 *
 * @return  ptr to new token (or 0).
 */
CToken* nsTokenAllocator::CreateTokenOfType(eHTMLTokenTypes aType,eHTMLTags aTag, const nsAReadableString& aString) {

  CToken* result=0;

#ifdef  NS_DEBUG
    mTotals[aType-1]++;
#endif
  switch(aType){
    case eToken_start:            result=new(mArenaPool) CStartToken(aString, aTag); break;
    case eToken_end:              result=new(mArenaPool) CEndToken(aString, aTag); break;
    case eToken_comment:          result=new(mArenaPool) CCommentToken(aString); break;
    case eToken_entity:           result=new(mArenaPool) CEntityToken(aString); break;
    case eToken_whitespace:       result=new(mArenaPool) CWhitespaceToken(aString); break;
    case eToken_newline:          result=new(mArenaPool) CNewlineToken(); break;
    case eToken_text:             result=new(mArenaPool) CTextToken(aString); break;
    case eToken_attribute:        result=new(mArenaPool) CAttributeToken(aString); break;
    case eToken_script:           result=new(mArenaPool) CScriptToken(aString); break;
    case eToken_style:            result=new(mArenaPool) CStyleToken(aString); break;
    case eToken_instruction:      result=new(mArenaPool) CInstructionToken(aString); break;
    case eToken_cdatasection:     result=new(mArenaPool) CCDATASectionToken(aString); break;
    case eToken_error:            result=new(mArenaPool) CErrorToken(); break;
    case eToken_doctypeDecl:      result=new(mArenaPool) CDoctypeDeclToken(aString); break;
    case eToken_markupDecl:       result=new(mArenaPool) CMarkupDeclToken(aString); break;
      default:
        NS_ASSERTION(PR_FALSE, "nsDTDUtils::CreateTokenOfType: illegal token type"); 
        break;
  }

  return result;
}

/**
 * Let's get this code ready to be reused by all the contexts.
 * 
 * @update	rickg 12June2000
 * @param   aType -- tells you the type of token to create
 * @param   aTag  -- tells you the type of tag to init with this token
 *
 * @return  ptr to new token (or 0).
 */
CToken* nsTokenAllocator::CreateTokenOfType(eHTMLTokenTypes aType,eHTMLTags aTag) {

  CToken* result=0;

#ifdef  NS_DEBUG
    mTotals[aType-1]++;
#endif
  switch(aType){
    case eToken_start:            result=new(mArenaPool) CStartToken(aTag); break;
    case eToken_end:              result=new(mArenaPool) CEndToken(aTag); break;
    case eToken_comment:          result=new(mArenaPool) CCommentToken(); break;
    case eToken_attribute:        result=new(mArenaPool) CAttributeToken(); break;
    case eToken_entity:           result=new(mArenaPool) CEntityToken(); break;
    case eToken_whitespace:       result=new(mArenaPool) CWhitespaceToken(); break;
    case eToken_newline:          result=new(mArenaPool) CNewlineToken(); break;
    case eToken_text:             result=new(mArenaPool) CTextToken(); break;
    case eToken_script:           result=new(mArenaPool) CScriptToken(); break;
    case eToken_style:            result=new(mArenaPool) CStyleToken(); break;
    case eToken_instruction:      result=new(mArenaPool) CInstructionToken(); break;
    case eToken_cdatasection:     result=new(mArenaPool) CCDATASectionToken(); break;
    case eToken_error:            result=new(mArenaPool) CErrorToken(); break;
    case eToken_doctypeDecl:      result=new(mArenaPool) CDoctypeDeclToken(aTag); break;
    case eToken_markupDecl:       result=new(mArenaPool) CMarkupDeclToken(); break;
    default:
      NS_ASSERTION(PR_FALSE, "nsDTDUtils::CreateTokenOfType: illegal token type"); 
      break;
   }

  return result;
}

#ifdef DEBUG_TRACK_NODES 

static nsCParserNode* gAllNodes[100];
static int gAllNodeCount=0;

int FindNode(nsCParserNode *aNode) {
  int theIndex=0;
  for(theIndex=0;theIndex<gAllNodeCount;theIndex++) {
    if(gAllNodes[theIndex]==aNode) {
      return theIndex;
    }
  }
  return -1;
}

void AddNode(nsCParserNode *aNode) {
  if(-1==FindNode(aNode)) {
    gAllNodes[gAllNodeCount++]=aNode;
  }
  else {
    //you tried to recycle a node twice!
  }
}

void RemoveNode(nsCParserNode *aNode) {
  int theIndex=FindNode(aNode);
  if(-1<theIndex) {
    gAllNodes[theIndex]=gAllNodes[--gAllNodeCount];
  }
}

#endif 


#ifdef HEAP_ALLOCATED_NODES
nsNodeAllocator::nsNodeAllocator():mSharedNodes(0){
#ifdef DEBUG_TRACK_NODES
  mCount=0;
#endif
#else 
  static const size_t  kNodeBuckets[]       ={sizeof(nsCParserNode)};
  static const PRInt32 kNumNodeBuckets      = sizeof(kNodeBuckets) / sizeof(size_t);
  static const PRInt32 kInitialNodePoolSize = NS_SIZE_IN_HEAP(sizeof(nsCParserNode)) * 50;
nsNodeAllocator::nsNodeAllocator() {
  mNodePool.Init("NodePool", kNodeBuckets, kNumNodeBuckets, kInitialNodePoolSize);
#endif
  MOZ_COUNT_CTOR(nsNodeAllocator);
}
  
nsNodeAllocator::~nsNodeAllocator() {
  MOZ_COUNT_DTOR(nsNodeAllocator);

#ifdef HEAP_ALLOCATED_NODES
  nsCParserNode* theNode=0;

  while((theNode=(nsCParserNode*)mSharedNodes.Pop())){
#ifdef DEBUG_TRACK_NODES
    RemoveNode(theNode);
#endif
    ::operator delete(theNode); 
    theNode=nsnull;
  }
#ifdef DEBUG_TRACK_NODES
  if(mCount) {
    printf("**************************\n");
    printf("%i out of %i nodes leaked!\n",gAllNodeCount,mCount);
    printf("**************************\n");
  }
#endif
#endif
}
  
nsCParserNode* nsNodeAllocator::CreateNode(CToken* aToken,PRInt32 aLineNumber,nsTokenAllocator* aTokenAllocator) {
  nsCParserNode* result=0;

#ifdef HEAP_ALLOCATED_NODES
#if 0
  if(gAllNodeCount!=mSharedNodes.GetSize()) {
    int x=10; //this is very BAD!
  }
#endif

  result=NS_STATIC_CAST(nsCParserNode*,mSharedNodes.Pop());
  if(result) {
    result->Init(aToken,aLineNumber,aTokenAllocator,this);
  }
  else{
    result=nsCParserNode::Create(aToken,aLineNumber,aTokenAllocator,this);
#ifdef DEBUG_TRACK_NODES
    mCount++;
    AddNode(NS_STATIC_CAST(nsCParserNode*,result));
#endif
    IF_HOLD(result);
  }
#else
  result=nsCParserNode::Create(aToken,aLineNumber,aTokenAllocator,this);
  IF_HOLD(result);
#endif
  return result;
}

#ifdef DEBUG
void DebugDumpContainmentRules(nsIDTD& theDTD,const char* aFilename,const char* aTitle) {
#ifdef RICKG_DEBUG

#include <fstream.h>

  const char* prefix="     ";
  fstream out(aFilename,ios::out);
  out << "==================================================" << endl;
  out << aTitle << endl;
  out << "==================================================";
  int i,j=0;
  int written;
  for(i=1;i<eHTMLTag_text;i++){
    const char* tag=nsHTMLTags::GetCStringValue((eHTMLTags)i);
    out << endl << endl << "Tag: <" << tag << ">" << endl;
    out << prefix;
    written=0;
    if(theDTD.IsContainer(i)) {
      for(j=1;j<eHTMLTag_text;j++){
        if(15==written) {
          out << endl << prefix;
          written=0;
        }
        if(theDTD.CanContain(i,j)){
          tag=nsHTMLTags::GetCStringValue((eHTMLTags)j);
          if(tag) {
            out<< tag << ", ";
            written++;
          }
        }
      }//for
    }
    else out<<"(not container)" << endl;
  }
#endif
}
#endif


/*************************************************************************
 *  The table lookup technique was adapted from the algorithm described  *
 *  by Avram Perez, Byte-wise CRC Calculations, IEEE Micro 3, 40 (1983). *
 *************************************************************************/

#define POLYNOMIAL 0x04c11db7L

static PRBool crc_table_initialized;
static PRUint32 crc_table[256];

static void gen_crc_table() {
 /* generate the table of CRC remainders for all possible bytes */
  int i, j;  
  PRUint32 crc_accum;
  for ( i = 0;  i < 256;  i++ ) { 
    crc_accum = ( (unsigned long) i << 24 );
    for ( j = 0;  j < 8;  j++ ) { 
      if ( crc_accum & 0x80000000L )
        crc_accum = ( crc_accum << 1 ) ^ POLYNOMIAL;
      else crc_accum = ( crc_accum << 1 ); 
    }
    crc_table[i] = crc_accum; 
  }
  return; 
}

PRUint32 AccumulateCRC(PRUint32 crc_accum, char *data_blk_ptr, int data_blk_size)  {
  if (!crc_table_initialized) {
    gen_crc_table();
    crc_table_initialized = PR_TRUE;
  }

 /* update the CRC on the data block one byte at a time */
  int i, j;
  for ( j = 0;  j < data_blk_size;  j++ ) { 
    i = ( (int) ( crc_accum >> 24) ^ *data_blk_ptr++ ) & 0xff;
    crc_accum = ( crc_accum << 8 ) ^ crc_table[i]; 
  }
  return crc_accum; 
}

/**************************************************************
  This defines the topic object used by the observer service.
  The observerService uses a list of these, 1 per topic when
  registering tags.
 **************************************************************/
NS_IMPL_ISUPPORTS1(nsObserverEntry, nsIObserverEntry)

nsObserverEntry::nsObserverEntry(const nsAString& aTopic) : mTopic(aTopic) 
{
  NS_INIT_ISUPPORTS();
  memset(mObservers, 0, sizeof(mObservers));
}

nsObserverEntry::~nsObserverEntry() {
  for (PRInt32 i = 0; i <= NS_HTML_TAG_MAX; i++){
    if (mObservers[i]) {
      PRInt32 count = mObservers[i]->Count();
      for (PRInt32 j = 0; j < count; j++) {
        nsISupports* obs = (nsISupports*)mObservers[i]->ElementAt(j);
        NS_IF_RELEASE(obs);
      }
      delete mObservers[i];
    }
  }
}

NS_IMETHODIMP
nsObserverEntry::Notify(nsIParserNode* aNode,
                        nsIParser* aParser,
                        nsISupports* aWebShell) 
{
  NS_ENSURE_ARG_POINTER(aNode);
  NS_ENSURE_ARG_POINTER(aParser);

  nsresult result = NS_OK;
  eHTMLTags theTag = (eHTMLTags)aNode->GetNodeType();
 
  if (theTag <= NS_HTML_TAG_MAX) {
    nsVoidArray*  theObservers = mObservers[theTag];
    if (theObservers) {
      nsAutoString      theCharsetValue;
      PRInt32   theCharsetSource;
      aParser->GetDocumentCharset(theCharsetValue,theCharsetSource);

      PRInt32 theAttrCount = aNode->GetAttributeCount(); 
      PRInt32 theObserversCount = theObservers->Count(); 
      if (0 < theObserversCount){
        nsStringArray keys(theAttrCount+4), values(theAttrCount+4);

        // XXX this and the following code may be a performance issue.
        // Every key and value is copied and added to an voidarray (causing at
        // least 2 allocations for mImpl, usually more, plus at least 1 per
        // string (total = 2*(keys+3) + 2(or more) array allocations )).
        PRInt32 index;
        for (index = 0; index < theAttrCount; index++) {
          keys.AppendString(aNode->GetKeyAt(index));
          values.AppendString(aNode->GetValueAt(index));
        } 

        nsAutoString intValue;

        keys.AppendString(NS_LITERAL_STRING("charset")); 
        values.AppendString(theCharsetValue);       
      
        keys.AppendString(NS_LITERAL_STRING("charsetSource")); 
        intValue.AppendInt(PRInt32(theCharsetSource),10);
        values.AppendString(intValue); 

        keys.AppendString(NS_LITERAL_STRING("X_COMMAND"));
        values.AppendString(NS_LITERAL_STRING("text/html")); 

        nsCOMPtr<nsIChannel> channel;
        aParser->GetChannel(getter_AddRefs(channel));

        for (index=0;index<theObserversCount;index++) {
          nsIElementObserver* observer = NS_STATIC_CAST(nsIElementObserver*,theObservers->ElementAt(index));
          if (observer) {
            result = observer->Notify(aWebShell, channel,
                                      nsHTMLTags::GetStringValue(theTag),
                                      &keys, &values);
            if (NS_FAILED(result)) {
              break;
            }
          }
        } 
      } 
    }
  }
  return result;
}

PRBool 
nsObserverEntry::Matches(const nsAString& aString) {
  PRBool result = aString.Equals(mTopic);
  return result;
}

nsresult
nsObserverEntry::AddObserver(nsIElementObserver *aObserver,
                             eHTMLTags aTag) 
{
  if (aObserver) {
    if (!mObservers[aTag]) {
      mObservers[aTag] = new nsAutoVoidArray();
      if (!mObservers[aTag]) {
        return NS_ERROR_OUT_OF_MEMORY;
      }
    }
    NS_ADDREF(aObserver);
    mObservers[aTag]->AppendElement(aObserver);
  }
  return NS_OK;
}

void 
nsObserverEntry::RemoveObserver(nsIElementObserver *aObserver)
{
  for (PRInt32 i=0; i <= NS_HTML_TAG_MAX; i++){
    if (mObservers[i]) {
      nsISupports* obs = aObserver;
      PRBool removed = mObservers[i]->RemoveElement(obs);
      if (removed) {
        NS_RELEASE(obs);
      }
    }
  }
}
