rickg%netscape.com 8f5813c0a2 WIP for token reduction and a few bug fixes
git-svn-id: svn://10.0.0.236/trunk@7298 18797224-902f-48f8-a5cc-f745e15eee43
1998-08-05 02:01:44 +00:00

3276 lines
91 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsIDTDDebug.h"
#include "CNavDTD.h"
#include "nsHTMLTokens.h"
#include "nsCRT.h"
#include "nsParser.h"
#include "nsIHTMLContentSink.h"
#include "nsScanner.h"
#include "nsParserTypes.h"
#include "nsVoidArray.h"
#include "nsTokenHandler.h"
#include "nsIDTDDebug.h"
#include "prenv.h" //this is here for debug reasons...
#include "prtypes.h" //this is here for debug reasons...
#include "prio.h"
#include "plstr.h"
#ifdef XP_PC
#include <direct.h> //this is here for debug reasons...
#endif
#include "prmem.h"
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kIDTDIID, NS_IDTD_IID);
static NS_DEFINE_IID(kClassIID, NS_INAVHTML_DTD_IID);
//static const char* kNullURL = "Error: Null URL given";
//static const char* kNullFilename= "Error: Null filename given";
static const char* kNullToken = "Error: Null token given";
static const char* kInvalidTagStackPos = "Error: invalid tag stack position";
static const char* kHTMLTextContentType = "text/html";
static char* kVerificationDir = "c:/temp";
static nsAutoString gEmpty;
static char formElementTags[]= {
eHTMLTag_button, eHTMLTag_fieldset, eHTMLTag_input,
eHTMLTag_isindex, eHTMLTag_label, eHTMLTag_legend,
eHTMLTag_option, eHTMLTag_select, eHTMLTag_textarea,0};
static char gHeadingTags[]={
eHTMLTag_h1, eHTMLTag_h2, eHTMLTag_h3,
eHTMLTag_h4, eHTMLTag_h5, eHTMLTag_h6,
0};
static char gStyleTags[]={
eHTMLTag_a,
eHTMLTag_acronym,
eHTMLTag_b,
eHTMLTag_bdo,
eHTMLTag_big,
eHTMLTag_blink,
eHTMLTag_cite,
eHTMLTag_code,
eHTMLTag_del,
eHTMLTag_dfn,
eHTMLTag_em,
eHTMLTag_font,
eHTMLTag_i,
eHTMLTag_ins,
eHTMLTag_kbd,
eHTMLTag_nobr,
eHTMLTag_q,
eHTMLTag_s,
eHTMLTag_samp,
eHTMLTag_small,
eHTMLTag_span,
eHTMLTag_strike,
eHTMLTag_strong,
eHTMLTag_sub,
eHTMLTag_sup,
eHTMLTag_tt,
eHTMLTag_u,
eHTMLTag_var,
0};
static char gTableTags[]={
eHTMLTag_caption, eHTMLTag_col, eHTMLTag_colgroup, eHTMLTag_tbody,
eHTMLTag_tfoot, eHTMLTag_tr, eHTMLTag_thead, eHTMLTag_td,
0};
static char gWhitespaceTags[]={
eHTMLTag_newline, eHTMLTag_whitespace,
0};
/************************************************************************
CTagStack class implementation.
The reason we use this class is so that we can view the stack state
in the usual way via the debugger.
************************************************************************/
CTagStack::CTagStack(int aDefaultSize) {
#ifdef _dynstack
mSize=aDefaultSize;
mTags =new eHTMLTags[mSize];
mBits =new PRBool[mSize];
#else
mSize=eStackSize;
#endif
mCount=0;
mPrevious=0;
nsCRT::zero(mTags,mSize*sizeof(eHTMLTags));
nsCRT::zero(mBits,mSize*sizeof(PRBool));
}
/**
* Default constructor
* @update gess7/9/98
* @param aDefaultsize tells the stack what size to start out.
* however, we'll autosize as needed.
*/
CTagStack::~CTagStack() {
#ifdef _dynstack
delete mTags;
delete mBits;
mTags=0;
mBits=0;
#endif
mSize=mCount=0;
}
/**
*
* @update gess7/9/98
* @param
* @return
*/
void CTagStack::Push(eHTMLTags aTag) {
if(mCount>=mSize) {
#ifdef _dynstack
eHTMLTags* tmp=new eHTMLTags[2*mSize];
nsCRT::zero(tmp,2*mSize*sizeof(eHTMLTag_html));
nsCRT::memcpy(tmp,mTags,mSize*sizeof(eHTMLTag_html));
delete mTags;
mTags=tmp;
PRBool* tmp2=new PRBool[2*mSize];
nsCRT::zero(tmp2,2*mSize*sizeof(PRBool));
nsCRT::memcpy(tmp2,mBits,mSize*sizeof(PRBool));
delete mBits;
mBits=tmp2;
mSize*=2;
#endif
}
mTags[mCount++]=aTag;
}
/**
*
* @update gess7/9/98
* @param
* @return
*/
eHTMLTags CTagStack::Pop() {
eHTMLTags result=eHTMLTag_unknown;
if(mCount>0) {
result=mTags[--mCount];
mTags[mCount]=eHTMLTag_unknown;
mBits[mCount]=PR_FALSE;
}
return result;
}
/**
*
* @update gess7/9/98
* @param
* @return
*/
eHTMLTags CTagStack::First() const {
if(mCount>0)
return mTags[0];
return eHTMLTag_unknown;
}
/**
*
* @update gess7/9/98
* @param
* @return
*/
eHTMLTags CTagStack::Last() const {
if(mCount>0)
return mTags[mCount-1];
return eHTMLTag_unknown;
}
/**************************************************************
Now define the tokenrecycler class...
**************************************************************/
/************************************************************************
CTokenRecycler class implementation.
This class is used to recycle tokens.
By using this simple class, we cut WAY down on the number of tokens
that get created during the run of the system.
************************************************************************/
class nsCTokenRecycler : public nsITokenRecycler {
public:
enum {eCacheMaxSize=50};
nsCTokenRecycler();
virtual ~nsCTokenRecycler();
virtual void RecycleToken(CToken* aToken);
virtual CToken* CreateTokenOfType(eHTMLTokenTypes aType,eHTMLTags aTag, const nsString& aString);
protected:
CToken* mTokenCache[eToken_last-1][eCacheMaxSize];
PRInt32 mCount[eToken_last-1];
};
/**
*
* @update gess7/25/98
* @param
*/
nsCTokenRecycler::nsCTokenRecycler() : nsITokenRecycler() {
nsCRT::zero(mTokenCache,sizeof(mTokenCache));
nsCRT::zero(mCount,sizeof(mCount));
}
/**
* Destructor for the token factory
* @update gess7/25/98
*/
nsCTokenRecycler::~nsCTokenRecycler() {
}
/**
* This method gets called when someone wants to recycle a token
* @update gess7/24/98
* @param aToken -- token to be recycled.
* @return nada
*/
void nsCTokenRecycler::RecycleToken(CToken* aToken) {
if(aToken) {
PRInt32 theType=aToken->GetTokenType();
if(mCount[theType-1]<eCacheMaxSize) {
mTokenCache[theType-1][mCount[theType-1]]=aToken;
mCount[theType-1]++;
} else {
//this is an overflow condition. More tokens of a given
//type have been created than we can store in our recycler.
//In this case, just destroy the extra token.
delete aToken;
}
}
}
/**
*
* @update gess8/4/98
* @param
* @return
*/
CToken* nsCTokenRecycler::CreateTokenOfType(eHTMLTokenTypes aType,eHTMLTags aTag, const nsString& aString) {
CToken* result;
if(0<mCount[aType-1]) {
result=mTokenCache[aType-1][mCount[aType-1]-1];
result->Reinitialize(aTag,aString);
mTokenCache[aType-1][mCount[aType-1]-1]=0;
mCount[aType-1]--;
}
else {
switch(aType){
case eToken_start: result=new CStartToken(aTag); break;
case eToken_end: result=new CEndToken(aTag); break;
case eToken_comment: result=new CCommentToken(); break;
case eToken_attribute: result=new CAttributeToken(); break;
case eToken_entity: result=new CEntityToken(); break;
case eToken_whitespace: result=new CWhitespaceToken(); break;
case eToken_newline: result=new CNewlineToken(); break;
case eToken_text: result=new CTextToken(aString); break;
case eToken_script: result=new CScriptToken(); break;
case eToken_style: result=new CStyleToken(); break;
case eToken_skippedcontent: result=new CSkippedContentToken(aString); break;
default:
break;
}
}
return result;
}
nsCTokenRecycler gTokenRecycler;
/************************************************************************
And now for the main class -- CNavDTD...
************************************************************************/
/**
* This method gets called as part of our COM-like interfaces.
* Its purpose is to create an interface to parser object
* of some type.
*
* @update gess 4/8/98
* @param nsIID id of object to discover
* @param aInstancePtr ptr to newly discovered interface
* @return NS_xxx result code
*/
nsresult CNavDTD::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
if(aIID.Equals(kISupportsIID)) { //do IUnknown...
*aInstancePtr = (nsIDTD*)(this);
}
else if(aIID.Equals(kIDTDIID)) { //do IParser base class...
*aInstancePtr = (nsIDTD*)(this);
}
else if(aIID.Equals(kClassIID)) { //do this class...
*aInstancePtr = (CNavDTD*)(this);
}
else {
*aInstancePtr=0;
return NS_NOINTERFACE;
}
((nsISupports*) *aInstancePtr)->AddRef();
return NS_OK;
}
/**
* This method is defined in nsIParser. It is used to
* cause the COM-like construction of an nsParser.
*
* @update gess 4/8/98
* @param nsIParser** ptr to newly instantiated parser
* @return NS_xxx error result
*/
NS_HTMLPARS nsresult NS_NewNavHTMLDTD(nsIDTD** aInstancePtrResult)
{
CNavDTD* it = new CNavDTD();
if (it == 0) {
return NS_ERROR_OUT_OF_MEMORY;
}
return it->QueryInterface(kClassIID, (void **) aInstancePtrResult);
}
NS_IMPL_ADDREF(CNavDTD)
NS_IMPL_RELEASE(CNavDTD)
/**
*
*
* @update gess 6/9/98
* @param
* @return
*/
PRInt32 NavDispatchTokenHandler(CToken* aToken,nsIDTD* aDTD) {
PRInt32 result=0;
CHTMLToken* theToken= (CHTMLToken*)(aToken);
eHTMLTokenTypes theType= (eHTMLTokenTypes)theToken->GetTokenType();
CNavDTD* theDTD=(CNavDTD*)aDTD;
if(aDTD) {
switch(theType) {
case eToken_start:
result=theDTD->HandleStartToken(aToken); break;
case eToken_end:
result=theDTD->HandleEndToken(aToken); break;
case eToken_comment:
result=theDTD->HandleCommentToken(aToken); break;
case eToken_entity:
result=theDTD->HandleEntityToken(aToken); break;
case eToken_whitespace:
result=theDTD->HandleStartToken(aToken); break;
case eToken_newline:
result=theDTD->HandleStartToken(aToken); break;
case eToken_text:
result=theDTD->HandleStartToken(aToken); break;
case eToken_attribute:
result=theDTD->HandleAttributeToken(aToken); break;
case eToken_style:
result=theDTD->HandleStyleToken(aToken); break;
case eToken_skippedcontent:
result=theDTD->HandleSkippedContentToken(aToken); break;
default:
result=0;
}//switch
}//if
return result;
}
/**
* init the set of default token handlers...
*
* @update gess 3/25/98
* @param
* @return
*/
void CNavDTD::InitializeDefaultTokenHandlers() {
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_start));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_end));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_comment));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_entity));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_whitespace));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_newline));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_text));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_attribute));
// AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_script));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_style));
AddTokenHandler(new CTokenHandler(NavDispatchTokenHandler,eToken_skippedcontent));
}
class CNavTokenDeallocator: public nsDequeFunctor{
public:
virtual void* operator()(void* anObject) {
CToken* aToken = (CToken*)anObject;
delete aToken;
return 0;
}
};
static CNavTokenDeallocator gTokenKiller;
/**
* Default constructor
*
* @update gess 4/9/98
* @param
* @return
*/
CNavDTD::CNavDTD() : nsIDTD(), mContextStack(), mTokenDeque(gTokenKiller) {
NS_INIT_REFCNT();
mParser=0;
mSink = nsnull;
mDTDDebug=0;
mLineNumber=1;
mStyleStack=new CTagStack();
nsCRT::zero(mTokenHandlers,sizeof(mTokenHandlers));
mHasOpenForm=PR_FALSE;
mHasOpenMap=PR_FALSE;
InitializeDefaultTokenHandlers();
}
/**
* Default destructor
*
* @update gess 4/9/98
* @param
* @return
*/
CNavDTD::~CNavDTD(){
DeleteTokenHandlers();
delete mStyleStack;
NS_IF_RELEASE(mDTDDebug);
}
/**
* Call this method if you want the DTD to construct a fresh
* instance of itself.
* @update gess7/23/98
* @param
* @return
*/
nsresult CNavDTD::CreateNewInstance(nsIDTD** aInstancePtrResult){
return NS_NewNavHTMLDTD(aInstancePtrResult);
}
/**
*
* @update gess8/4/98
* @param
* @return
*/
nsITokenRecycler* CNavDTD::GetTokenRecycler(void){
return 0;
return &gTokenRecycler;
}
/**
* Called by the parser to initiate dtd verification of the
* internal context stack.
* @update gess 7/23/98
* @param
* @return
*/
PRBool CNavDTD::Verify(nsString& aURLRef){
PRBool result=PR_TRUE;
if(!mDTDDebug){;
nsresult rval = NS_NewDTDDebug(&mDTDDebug);
if (NS_OK != rval) {
fputs("Cannot create parser debugger.\n", stdout);
result=-PR_FALSE;
}
else mDTDDebug->SetVerificationDirectory(kVerificationDir);
}
if(mDTDDebug) {
mDTDDebug->Verify(this,mParser,mContextStack.mCount,mContextStack.mTags,aURLRef);
}
return result;
}
/**
* This method adds a new parser context to the list,
* pushing the current one to the next position.
* @update gess7/22/98
* @param ptr to new context
* @return nada
*/
void CNavDTD::PushStack(CTagStack& aStack) {
aStack.mPrevious=mStyleStack;
mStyleStack=&aStack;
}
/**
* This method pops the topmost context off the stack,
* returning it to the user. The next context (if any)
* becomes the current context.
* @update gess7/22/98
* @return prev. context
*/
CTagStack* CNavDTD::PopStack() {
CTagStack* oldStack=mStyleStack;
if(oldStack) {
mStyleStack=oldStack->mPrevious;
}
return oldStack;
}
/**
* This method is called to determine if the given DTD can parse
* a document in a given source-type.
* NOTE: Parsing always assumes that the end result will involve
* storing the result in the main content model.
* @update gess6/24/98
* @param
* @return TRUE if this DTD can satisfy the request; FALSE otherwise.
*/
PRBool CNavDTD::CanParse(nsString& aContentType, PRInt32 aVersion){
PRBool result=aContentType.Equals(kHTMLTextContentType);
return result;
}
/**
*
* @update gess7/7/98
* @param
* @return
*/
eAutoDetectResult CNavDTD::AutoDetectContentType(nsString& aBuffer,nsString& aType){
eAutoDetectResult result=eUnknownDetect;
if(PR_TRUE==aType.Equals(kHTMLTextContentType))
result=eValidDetect;
return result;
}
/**
*
* @update gess5/18/98
* @param
* @return
*/
nsresult CNavDTD::WillBuildModel(nsString& aFilename,PRBool aNotifySink){
nsresult result=NS_OK;
mFilename=aFilename;
if((aNotifySink) && (mSink)) {
mLineNumber=1;
result = mSink->WillBuildModel();
}
return result;
}
/**
*
* @update gess5/18/98
* @param
* @return
*/
nsresult CNavDTD::DidBuildModel(PRInt32 anErrorCode,PRBool aNotifySink){
nsresult result= NS_OK;
if((kNoError==anErrorCode) && (mContextStack.mCount>0)) {
result = CloseContainersTo(0,eHTMLTag_unknown,PR_FALSE);
}
if((aNotifySink) && (mSink)) {
result = mSink->DidBuildModel(1);
}
if(mDTDDebug) {
mDTDDebug->DumpVectorRecord();
}
return result;
}
/**
* This big dispatch method is used to route token handler calls to the right place.
* What's wrong with it? This table, and the dispatch methods themselves need to be
* moved over to the delegate. Ah, so much to do...
*
* @update gess 5/21/98
* @param aType
* @param aToken
* @param aParser
* @return
*/
nsresult CNavDTD::HandleToken(CToken* aToken){
nsresult result=NS_OK;
if(aToken) {
CHTMLToken* theToken= (CHTMLToken*)(aToken);
eHTMLTokenTypes theType=eHTMLTokenTypes(theToken->GetTokenType());
CITokenHandler* theHandler=GetTokenHandler(theType);
if(theHandler) {
result=(*theHandler)(theToken,this);
if (mDTDDebug)
mDTDDebug->Verify(this, mParser, mContextStack.mCount, mContextStack.mTags, mFilename);
}
}//if
return result;
}
/**
* This method gets called when a start token has been
* encountered in the parse process. If the current container
* can contain this tag, then add it. Otherwise, you have
* two choices: 1) create an implicit container for this tag
* to be stored in
* 2) close the top container, and add this to
* whatever container ends up on top.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @param aNode -- CParserNode representing this start token
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleDefaultStartToken(CToken* aToken,eHTMLTags aChildTag,nsIParserNode& aNode) {
NS_PRECONDITION(0!=aToken,kNullToken);
eHTMLTags parentTag=GetTopNode();
nsresult result=NS_OK;
PRBool contains=CanContain(parentTag,aChildTag);
if(PR_FALSE==contains){
result=CreateContextStackFor(aChildTag);
if(NS_OK!=result) {
//if you're here, then the new topmost container can't contain aToken.
//You must determine what container hierarchy you need to hold aToken,
//and create that on the parsestack.
result=ReduceContextStackFor(aChildTag);
if(PR_FALSE==CanContain(GetTopNode(),aChildTag)) {
//we unwound too far; now we have to recreate a valid context stack.
result=CreateContextStackFor(aChildTag);
}
}
}
if(IsContainer(aChildTag)){
if(PR_TRUE==mContextStack.mBits[mContextStack.mCount-1]) {
CloseTransientStyles(aChildTag);
}
result=OpenContainer(aNode,PR_TRUE);
}
else {
if(PR_FALSE==mContextStack.mBits[mContextStack.mCount-1]) {
OpenTransientStyles(aChildTag);
}
result=AddLeaf(aNode);
}
return result;
}
/**
* This method gets called when a start token has been
* encountered in the parse process. If the current container
* can contain this tag, then add it. Otherwise, you have
* two choices: 1) create an implicit container for this tag
* to be stored in
* 2) close the top container, and add this to
* whatever container ends up on top.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @param aNode -- CParserNode representing this start token
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleStartToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
CStartToken* st= (CStartToken*)(aToken);
eHTMLTags tokenTagType=(eHTMLTags)st->GetTypeID();
//Begin by gathering up attributes...
nsCParserNode attrNode((CHTMLToken*)aToken,mLineNumber);
PRInt16 attrCount=aToken->GetAttributeCount();
PRInt32 theCount;
nsresult result=(0==attrCount)
? NS_OK
: CollectAttributes(attrNode,attrCount);
if(NS_OK==result) {
//now check to see if this token should be omitted...
if(PR_FALSE==CanOmit(GetTopNode(),tokenTagType)) {
switch(tokenTagType) {
case eHTMLTag_html:
result=OpenHTML(attrNode); break;
case eHTMLTag_title:
{
nsCParserNode theNode(st,mLineNumber);
result=OpenHead(theNode); //open the head...
if(NS_OK==result) {
result=CollectSkippedContent(attrNode,theCount);
mSink->SetTitle(attrNode.GetSkippedContent());
result=CloseHead(theNode); //close the head...
}
}
break;
case eHTMLTag_textarea:
{
CollectSkippedContent(attrNode,theCount);
result=AddLeaf(attrNode);
}
break;
case eHTMLTag_form:
result = OpenForm(attrNode);
break;
case eHTMLTag_meta:
case eHTMLTag_link:
{
nsCParserNode theNode((CHTMLToken*)aToken,mLineNumber);
result=OpenHead(theNode);
if(NS_OK==result)
result=AddLeaf(attrNode);
if(NS_OK==result)
result=CloseHead(theNode);
}
break;
case eHTMLTag_style:
{
nsCParserNode theNode((CHTMLToken*)aToken,mLineNumber);
result=OpenHead(theNode);
if(NS_OK==result) {
CollectSkippedContent(attrNode,theCount);
if(NS_OK==result) {
result=AddLeaf(attrNode);
if(NS_OK==result)
result=CloseHead(theNode);
}
}
}
break;
case eHTMLTag_script:
result=HandleScriptToken(st, attrNode); break;
case eHTMLTag_head:
break; //ignore head tags...
case eHTMLTag_base:
result=OpenHead(attrNode);
if(NS_OK==result) {
result=AddLeaf(attrNode);
if(NS_OK==result)
result=CloseHead(attrNode);
}
break;
case eHTMLTag_area:
if (mHasOpenMap) {
result = mSink->AddLeaf(attrNode);
}
break;
case eHTMLTag_map:
result = OpenMap(attrNode);
break;
default:
result=HandleDefaultStartToken(aToken,tokenTagType,attrNode);
break;
} //switch
} //if
} //if
if(eHTMLTag_newline==tokenTagType)
mLineNumber++;
return result;
}
/**
* This method gets called when an end token has been
* encountered in the parse process. If the end tag matches
* the start tag on the stack, then simply close it. Otherwise,
* we have a erroneous state condition. This can be because we
* have a close tag with no prior open tag (user error) or because
* we screwed something up in the parse process. I'm not sure
* yet how to tell the difference.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleEndToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
nsresult result=NS_OK;
CEndToken* et = (CEndToken*)(aToken);
eHTMLTags tokenTagType=(eHTMLTags)et->GetTypeID();
// Here's the hacky part:
// Because we're trying to be backward compatible with Nav4/5,
// we have to handle explicit styles the way it does. That means
// that we keep an internal style stack.When an EndToken occurs,
// we should see if it is an explicit style tag. If so, we can
// close the explicit style tag (goofy, huh?)
//now check to see if this token should be omitted, or
//if it's gated from closing by the presence of another tag.
if(PR_TRUE==CanOmitEndTag(GetTopNode(),tokenTagType)) {
UpdateStyleStackForCloseTag(tokenTagType,tokenTagType);
return result;
}
nsCParserNode theNode((CHTMLToken*)aToken,mLineNumber);
switch(tokenTagType) {
case eHTMLTag_style:
case eHTMLTag_link:
case eHTMLTag_meta:
case eHTMLTag_textarea:
case eHTMLTag_title:
case eHTMLTag_head:
case eHTMLTag_script:
break;
case eHTMLTag_map:
case eHTMLTag_form:
{
nsCParserNode aNode((CHTMLToken*)aToken,mLineNumber);
result=CloseContainer(aNode,tokenTagType,PR_FALSE);
}
break;
case eHTMLTag_td:
case eHTMLTag_th:
result=CloseContainersTo(tokenTagType,PR_TRUE);
// Empty the transient style stack (we just closed any extra
// ones off so it's safe to do it now) because they don't carry
// forward across table cell boundaries.
mStyleStack->mCount=0;
break;
default:
if(IsContainer(tokenTagType)){
result=CloseContainersTo(tokenTagType,PR_TRUE);
}
//
break;
}
return result;
}
/**
* This method gets called when an entity token has been
* encountered in the parse process.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleEntityToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
CEntityToken* et = (CEntityToken*)(aToken);
nsresult result=NS_OK;
eHTMLTags tokenTagType=(eHTMLTags)et->GetTypeID();
if(PR_FALSE==CanOmit(GetTopNode(),tokenTagType)) {
nsCParserNode aNode((CHTMLToken*)aToken,mLineNumber);
result=AddLeaf(aNode);
}
return result;
}
/**
* This method gets called when a comment token has been
* encountered in the parse process. After making sure
* we're somewhere in the body, we handle the comment
* in the same code that we use for text.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleCommentToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
return NS_OK;
}
/**
* This method gets called when a skippedcontent token has
* been encountered in the parse process. After verifying
* that the topmost container can contain text, we call
* AddLeaf to store this token in the top container.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleSkippedContentToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
nsresult result=NS_OK;
if(HasOpenContainer(eHTMLTag_body)) {
nsCParserNode aNode((CHTMLToken*)aToken,mLineNumber);
result=AddLeaf(aNode);
}
return result;
}
/**
* This method gets called when an attribute token has been
* encountered in the parse process. This is an error, since
* all attributes should have been accounted for in the prior
* start or end tokens
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleAttributeToken(CToken* aToken) {
NS_PRECONDITION(0!=aToken,kNullToken);
NS_ERROR("attribute encountered -- this shouldn't happen!");
return NS_OK;
}
/**
* This method gets called when a script token has been
* encountered in the parse process.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleScriptToken(CToken* aToken, nsCParserNode& aNode) {
NS_PRECONDITION(0!=aToken,kNullToken);
nsresult result=NS_OK;
PRInt32 pos=GetTopmostIndexOf(eHTMLTag_body);
PRInt32 attrCount=aToken->GetAttributeCount();
if (kNotFound == pos) {
// We're in the HEAD
result=OpenHead(aNode);
if(NS_OK==result) {
CollectSkippedContent(aNode,attrCount);
if(NS_OK==result) {
result=AddLeaf(aNode);
if(NS_OK==result)
result=CloseHead(aNode);
}
}
}
else {
// We're in the BODY
CollectSkippedContent(aNode,attrCount);
if(NS_OK==result) {
result=AddLeaf(aNode);
}
}
return result;
}
/**
* This method gets called when a style token has been
* encountered in the parse process.
*
* @update gess 3/25/98
* @param aToken -- next (start) token to be handled
* @return PR_TRUE if all went well; PR_FALSE if error occured
*/
nsresult CNavDTD::HandleStyleToken(CToken* aToken){
NS_PRECONDITION(0!=aToken,kNullToken);
// CStyleToken* st = (CStyleToken*)(aToken);
return NS_OK;
}
/**
* Retrieve the attributes for this node, and add then into
* the node.
*
* @update gess4/22/98
* @param aNode is the node you want to collect attributes for
* @param aCount is the # of attributes you're expecting
* @return error code (should be 0)
*/
PRInt32 CNavDTD::CollectAttributes(nsCParserNode& aNode,PRInt32 aCount){
/*
nsDequeIterator end=mParserContext->mTokenDeque.End();
int attr=0;
for(attr=0;attr<aCount;attr++) {
if(*mParserContext->mCurrentPos<end) {
CToken* tkn=(CToken*)(++(*mParserContext->mCurrentPos));
if(tkn){
if(eToken_attribute==eHTMLTokenTypes(tkn->GetTokenType())){
aNode.AddAttribute(tkn);
}
else (*mParserContext->mCurrentPos)--;
}
else return kInterrupted;
}
else return kInterrupted;
}
*/
int attr=0;
for(attr=0;attr<aCount;attr++){
CToken* theToken=mParser->PeekToken();
if(theToken) {
eHTMLTokenTypes theType=eHTMLTokenTypes(theToken->GetTokenType());
if(eToken_attribute==theType){
mParser->PopToken(); //pop it for real...
aNode.AddAttribute(theToken);
}
}
else return kInterrupted;
}
return kNoError;
}
/**
* Causes the next skipped-content token (if any) to
* be consumed by this node.
* @update gess5/11/98
* @param node to consume skipped-content
* @param holds the number of skipped content elements encountered
* @return Error condition.
*/
PRInt32 CNavDTD::CollectSkippedContent(nsCParserNode& aNode,PRInt32& aCount) {
PRInt32 result=kNoError;
eHTMLTokenTypes theType;
CToken* theToken;
aCount=0;
do{
CToken* theToken=mParser->PeekToken();
if(theToken) {
theType=eHTMLTokenTypes(theToken->GetTokenType());
if(eToken_skippedcontent==theType) {
mParser->PopToken();
aNode.SetSkippedContent(theToken);
aCount++;
}
}
} while(theToken && (eToken_skippedcontent==theType));
/*
eHTMLTokenTypes subtype=eToken_attribute;
nsDequeIterator end=mParserContext->mTokenDeque.End();
aCount=0;
while((*mParserContext->mCurrentPos!=end) && (eToken_attribute==subtype)) {
CToken* tkn=(CToken*)(++(*mParserContext->mCurrentPos));
subtype=eHTMLTokenTypes(tkn->GetTokenType());
if(eToken_skippedcontent==subtype) {
aNode.SetSkippedContent(tkn);
aCount++;
}
else (*mParserContext->mCurrentPos)--;
}
*/
return result;
}
/**
* Finds a tag handler for the given tag type, given in string.
*
* @update gess 4/2/98
* @param aString contains name of tag to be handled
* @return valid tag handler (if found) or null
*/
void CNavDTD::DeleteTokenHandlers(void) {
for(int i=eToken_unknown;i<eToken_last;i++){
delete mTokenHandlers[i];
mTokenHandlers[i]=0;
}
}
/**
* Finds a tag handler for the given tag type.
*
* @update gess 4/2/98
* @param aTagType type of tag to be handled
* @return valid tag handler (if found) or null
*/
CITokenHandler* CNavDTD::GetTokenHandler(eHTMLTokenTypes aType) const {
CITokenHandler* result=0;
if((aType>0) && (aType<eToken_last)) {
result=mTokenHandlers[aType];
}
else {
}
return result;
}
/**
* Register a handler.
*
* @update gess 4/2/98
* @param
* @return
*/
CITokenHandler* CNavDTD::AddTokenHandler(CITokenHandler* aHandler) {
NS_ASSERTION(0!=aHandler,"Error: Null handler");
if(aHandler) {
eHTMLTokenTypes type=(eHTMLTokenTypes)aHandler->GetTokenType();
if(type<eToken_last) {
mTokenHandlers[type]=aHandler;
}
else {
//add code here to handle dynamic tokens...
}
}
return 0;
}
/**
*
*
* @update gess 3/25/98
* @param
* @return
*/
void CNavDTD::SetParser(nsIParser* aParser) {
mParser=(nsParser*)aParser;
}
/**
* This method gets called in order to set the content
* sink for this parser to dump nodes to.
*
* @update gess 3/25/98
* @param nsIContentSink interface for node receiver
* @return
*/
nsIContentSink* CNavDTD::SetContentSink(nsIContentSink* aSink) {
nsIContentSink* old=mSink;
mSink=(nsIHTMLContentSink*)aSink;
return old;
}
/**
* This method is called to determine whether or not a tag
* can contain an explict style tag (font, italic, bold, etc.)
* Most can -- but some, like option, cannot. Therefore we
* don't bother to open transient styles within these elements.
*
* @update gess 4/8/98
* @param aParent -- tag enum of parent container
* @param aChild -- tag enum of child container
* @return PR_TRUE if parent can contain child
*/
PRBool CNavDTD::CanContainStyles(eHTMLTags aParent) const {
PRBool result=PR_TRUE;
switch(aParent) {
case eHTMLTag_option:
result=PR_FALSE; break;
default:
break;
}
return result;
}
/**
* This method is called to determine whether or not a
* form-type tag can contain a tag of another form-type tag.
*
* @update gess 4/8/98
* @param aParent -- tag enum of parent container
* @param aChild -- tag enum of child container
* @return PR_TRUE if parent can contain child
*/
PRBool CNavDTD::CanContainFormElement(eHTMLTags aParent,eHTMLTags aChild) const {
PRBool result=PR_FALSE;
if(mParser && HasOpenContainer(eHTMLTag_form)) {
eHTMLTags topTag=GetTopNode();
switch(aChild) {
case eHTMLTag_option:
result=PRBool(eHTMLTag_select==topTag); break;
default:
result=PR_TRUE;
break;
} //switch
}//if
return result;
}
/***********************************************************************************
The following tables determine the set of elements each tag can contain...
***********************************************************************************/
static char gTagSet1[]={
eHTMLTag_a, eHTMLTag_acronym, eHTMLTag_address, eHTMLTag_applet,
eHTMLTag_blink, eHTMLTag_b, eHTMLTag_basefont, eHTMLTag_bdo,
eHTMLTag_big,
eHTMLTag_blockquote,eHTMLTag_br, eHTMLTag_button, eHTMLTag_center,
eHTMLTag_cite, eHTMLTag_code, eHTMLTag_dfn, eHTMLTag_dir,
eHTMLTag_div, eHTMLTag_dl, eHTMLTag_em, eHTMLTag_fieldset,
eHTMLTag_embed,
eHTMLTag_font, eHTMLTag_form, eHTMLTag_h1, eHTMLTag_h2,
eHTMLTag_h3, eHTMLTag_h4, eHTMLTag_h5, eHTMLTag_h6,
eHTMLTag_hr, eHTMLTag_i, eHTMLTag_iframe, eHTMLTag_img,
eHTMLTag_input, eHTMLTag_isindex,
eHTMLTag_kbd, eHTMLTag_label, eHTMLTag_li,
eHTMLTag_map, eHTMLTag_menu, eHTMLTag_newline, eHTMLTag_nobr,
eHTMLTag_noframes, eHTMLTag_noscript,
eHTMLTag_object, eHTMLTag_ol, eHTMLTag_p, eHTMLTag_pre,
eHTMLTag_q, eHTMLTag_s, eHTMLTag_strike,
eHTMLTag_samp, eHTMLTag_script, eHTMLTag_select, eHTMLTag_small,
eHTMLTag_spacer, eHTMLTag_span, eHTMLTag_strong,
eHTMLTag_sub, eHTMLTag_sup, eHTMLTag_table, eHTMLTag_text,
eHTMLTag_textarea, eHTMLTag_tt, eHTMLTag_u, eHTMLTag_ul,
eHTMLTag_userdefined, eHTMLTag_var, eHTMLTag_wbr,
eHTMLTag_whitespace,
0};
static char gTagSet2[]={
eHTMLTag_a, eHTMLTag_acronym, eHTMLTag_applet, eHTMLTag_blink,
eHTMLTag_b,
eHTMLTag_basefont, eHTMLTag_bdo, eHTMLTag_big, eHTMLTag_br,
eHTMLTag_button, eHTMLTag_cite, eHTMLTag_code, eHTMLTag_dfn,
eHTMLTag_div, eHTMLTag_em, eHTMLTag_font, eHTMLTag_hr,
eHTMLTag_embed,
eHTMLTag_i, eHTMLTag_iframe, eHTMLTag_img, eHTMLTag_input,
eHTMLTag_kbd,
eHTMLTag_label, eHTMLTag_map, eHTMLTag_newline, eHTMLTag_nobr,
eHTMLTag_object, eHTMLTag_p,
eHTMLTag_q, eHTMLTag_s, eHTMLTag_strike,
eHTMLTag_samp, eHTMLTag_script, eHTMLTag_select, eHTMLTag_small,
eHTMLTag_spacer, eHTMLTag_span, eHTMLTag_strong,
eHTMLTag_sub, eHTMLTag_sup, eHTMLTag_text, eHTMLTag_textarea,
eHTMLTag_table,// XXX kipp was here
eHTMLTag_tt, eHTMLTag_u, eHTMLTag_userdefined, eHTMLTag_var,
eHTMLTag_wbr, eHTMLTag_whitespace,
0};
static char gTagSet3[]={
eHTMLTag_a, eHTMLTag_acronym, eHTMLTag_applet, eHTMLTag_blink,
eHTMLTag_b,
eHTMLTag_bdo, eHTMLTag_big, eHTMLTag_br, eHTMLTag_blockquote,
eHTMLTag_body, eHTMLTag_caption, eHTMLTag_center, eHTMLTag_cite,
eHTMLTag_code, eHTMLTag_dd, eHTMLTag_del, eHTMLTag_dfn,
eHTMLTag_div, eHTMLTag_dt, eHTMLTag_em, eHTMLTag_fieldset,
eHTMLTag_embed,
eHTMLTag_font, eHTMLTag_form, eHTMLTag_h1, eHTMLTag_h2,
eHTMLTag_h3, eHTMLTag_h4, eHTMLTag_h5, eHTMLTag_h6,
eHTMLTag_i, eHTMLTag_iframe, eHTMLTag_ins, eHTMLTag_kbd,
eHTMLTag_label, eHTMLTag_legend, eHTMLTag_li, eHTMLTag_newline,
eHTMLTag_noframes,
eHTMLTag_noscript, eHTMLTag_object, eHTMLTag_p, eHTMLTag_pre,
eHTMLTag_q, eHTMLTag_s, eHTMLTag_strike,
eHTMLTag_samp, eHTMLTag_small, eHTMLTag_spacer,
eHTMLTag_span, eHTMLTag_strong, eHTMLTag_sub, eHTMLTag_sup,
eHTMLTag_td, eHTMLTag_text,
eHTMLTag_th, eHTMLTag_tt, eHTMLTag_u, eHTMLTag_userdefined,
eHTMLTag_var, eHTMLTag_wbr, eHTMLTag_whitespace,
0};
/***********************************************************************************
The preceeding tables determine the set of elements each tag can contain...
***********************************************************************************/
/**
* This method is called to determine whether or not a tag
* of one type can contain a tag of another type.
*
* @update gess 4/8/98
* @param aParent -- tag enum of parent container
* @param aChild -- tag enum of child container
* @return PR_TRUE if parent can contain child
*/
PRBool CNavDTD::CanContain(PRInt32 aParent,PRInt32 aChild) {
PRBool result=PR_FALSE;
//This hack code is here because we don't yet know what to do
//with userdefined tags... XXX Hack
if(eHTMLTag_userdefined==aChild) // XXX Hack: For now...
result=PR_TRUE;
//handle form elements (this is very much a WIP!!!)
if(0!=strchr(formElementTags,aChild)){
return CanContainFormElement((eHTMLTags)aParent,(eHTMLTags)aChild);
}
if((aParent) && (0 != strchr(gStyleTags, aParent))) {
if(eHTMLTag_li == aChild) {
//This code was added to enforce the rule that listitems
//autoclose prior listitems. Stylistic tags (including <A>)
//that get in the way are simply out of luck.
result=PR_FALSE;
}
else
result=PRBool(0!=strchr(gTagSet1,aChild));
}
else {
switch((eHTMLTags)aParent) {
case eHTMLTag_address:
result=PRBool(0!=strchr(gTagSet2,aChild));
break;
case eHTMLTag_applet:
if(eHTMLTag_param==aChild)
result=PR_TRUE;
else
result=PRBool(0!=strchr(gTagSet2,aChild));
break;
case eHTMLTag_area:
case eHTMLTag_base:
case eHTMLTag_basefont:
case eHTMLTag_br:
case eHTMLTag_embed:
case eHTMLTag_hr:
case eHTMLTag_img:
case eHTMLTag_input:
case eHTMLTag_isindex:
case eHTMLTag_meta:
case eHTMLTag_spacer:
case eHTMLTag_wbr:
break; //singletons can't contain anything...
case eHTMLTag_blockquote:
case eHTMLTag_body:
if(eHTMLTag_userdefined==aChild)
result=PR_TRUE;
else
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_button:
result=PRBool(0!=strchr(gTagSet3,aChild));
break;
case eHTMLTag_caption:
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_center:
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_col:
case eHTMLTag_colgroup:
break; //singletons can't contain anything...
case eHTMLTag_dt:
{
static char datalistTags[]={eHTMLTag_dt,eHTMLTag_dd,0};
if(0!=strchr(datalistTags,aChild)) {
result=PR_TRUE;
}
else
result=PRBool(0!=strchr(gTagSet1,aChild));
}
break;
case eHTMLTag_dd:
case eHTMLTag_div:
result=PRBool(0!=strchr(gTagSet1,aChild)); break;
case eHTMLTag_dl:
{
char okTags[]={eHTMLTag_dd,eHTMLTag_dt,eHTMLTag_whitespace,
eHTMLTag_newline,eHTMLTag_p,0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_fieldset:
if(eHTMLTag_legend==aChild)
result=PR_TRUE;
else
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_form:
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_frame:
break; //singletons can't contain other tags
case eHTMLTag_frameset:
{
static char okTags[]={eHTMLTag_frame,eHTMLTag_frameset,
eHTMLTag_noframes,
eHTMLTag_newline,eHTMLTag_whitespace,0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_h1: case eHTMLTag_h2:
case eHTMLTag_h3: case eHTMLTag_h4:
case eHTMLTag_h5: case eHTMLTag_h6:
{
if(0!=strchr(gHeadingTags,aChild))
result=PR_FALSE;
else result=PRBool(0!=strchr(gTagSet1,aChild));
}
break;
case eHTMLTag_head:
{
static char okTags[]={
eHTMLTag_base, eHTMLTag_isindex, eHTMLTag_link, eHTMLTag_meta,
eHTMLTag_script, eHTMLTag_style, eHTMLTag_title, 0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_html:
{
static char okTags[]={eHTMLTag_body,eHTMLTag_frameset,eHTMLTag_head,0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_iframe:/* XXX wrong */
if(eHTMLTag_frame==aChild)
result=PR_FALSE;
else
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_label:
case eHTMLTag_legend:/* XXX not sure */
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_layer:
case eHTMLTag_link:
break; //singletons can't contain anything...
case eHTMLTag_li:
if (eHTMLTag_li == aChild) {
result = PR_FALSE;
}
else result=PRBool(!strchr(gHeadingTags,aChild));
break;
case eHTMLTag_listing:
result = PR_TRUE;
break;
case eHTMLTag_map:
{
static char okTags[] = {eHTMLTag_area,
eHTMLTag_newline,
eHTMLTag_whitespace,
0};
result = PRBool(0 != strchr(okTags, aChild));
}
break;
case eHTMLTag_menu:
case eHTMLTag_dir:
case eHTMLTag_ol:
case eHTMLTag_ul:
// XXX kipp was here
result=PRBool(0 != strchr(gTagSet1,aChild));
break;
case eHTMLTag_noframes:
if(eHTMLTag_body==aChild)
result=PR_TRUE;
else
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_noscript:
result=PRBool(0!=strchr(gTagSet1,aChild));
break;
case eHTMLTag_option:
//for now, allow an option to contain anything but another option...
result=PRBool(eHTMLTag_option!=aChild);
break;
case eHTMLTag_p:
{
static char datalistTags[]={eHTMLTag_dt,eHTMLTag_dd,0};
if(eHTMLTag_p==aChild)
result=PR_FALSE;
else if(0!=strchr(datalistTags,aChild)) {
//we now allow DT/DD inside a paragraph, so long as a DL is open...
if(PR_TRUE==HasOpenContainer(eHTMLTag_dl)) {
if(PR_TRUE==HasOpenContainer(eHTMLTag_dt))
result=PR_FALSE;
} else
result=PR_TRUE;
}
else
result=PRBool(0!=strchr(gTagSet2,aChild));
}
break;
case eHTMLTag_object:
case eHTMLTag_pre:
result=PRBool(0!=strchr(gTagSet2,aChild));
break;
case eHTMLTag_param:
break; //singletons can't contain other tags
case eHTMLTag_plaintext:
break;
case eHTMLTag_script:
break; //unadorned script text...
case eHTMLTag_select:
result=PR_TRUE; //for now, allow select to contain anything...
break;
case eHTMLTag_style:
break; //singletons can't contain other tags
case eHTMLTag_table:
{
static char okTags[]={
eHTMLTag_caption, eHTMLTag_col, eHTMLTag_colgroup,eHTMLTag_tbody,
eHTMLTag_tfoot, /* eHTMLTag_tr,*/ eHTMLTag_thead, 0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_tbody:
case eHTMLTag_tfoot:
case eHTMLTag_thead:
result=PRBool(eHTMLTag_tr==aChild);
break;
case eHTMLTag_th:
case eHTMLTag_td:
{
static char extraTags[]={eHTMLTag_newline,0};
result=PRBool(0!=strchr(extraTags,aChild));
if(PR_FALSE==result)
result=PRBool(0!=strchr(gTagSet1,aChild));
}
break;
case eHTMLTag_textarea:
case eHTMLTag_title:
break; //nothing but plain text...
case eHTMLTag_tr:
{
static char okTags[]={eHTMLTag_td,eHTMLTag_th,0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
case eHTMLTag_userdefined:
result=PR_TRUE; //XXX for now...
break;
case eHTMLTag_xmp:
break;
default:
#ifdef NS_DEBUG
printf("XXX: unhandled tag %s in CanContain switch statement\n",
NS_EnumToTag((nsHTMLTag)aParent));
#endif
break;
} //switch
} //if
return result;
}
/**
* This method is called to determine whether or not a tag
* of one type can contain a tag of another type.
*
* @update gess 4/8/98
* @param aParent -- tag enum of parent container
* @param aChild -- tag enum of child container
* @return PR_TRUE if parent can contain child
*/
PRBool CNavDTD::CanContainIndirect(eHTMLTags aParent,eHTMLTags aChild) const {
PRBool result=PR_FALSE;
switch(aParent) {
case eHTMLTag_html:
{
static char okTags[]={
eHTMLTag_head, eHTMLTag_body, 0
};
result=PRBool(0!=strchr(okTags,aChild));
}
case eHTMLTag_body:
result=PR_TRUE; break;
case eHTMLTag_table:
{
static char okTags[]={
eHTMLTag_caption, eHTMLTag_colgroup,
eHTMLTag_tbody, eHTMLTag_tfoot,
eHTMLTag_thead, eHTMLTag_tr,
eHTMLTag_td, eHTMLTag_th,
eHTMLTag_col, 0};
result=PRBool(0!=strchr(okTags,aChild));
}
break;
default:
break;
}
return result;
}
/**
* This method gets called to determine whether a given
* tag can contain newlines. Most do not.
*
* @update gess 3/25/98
* @param aTag -- tag to test for containership
* @return PR_TRUE if given tag can contain other tags
*/
PRBool CNavDTD::CanOmit(eHTMLTags aParent,eHTMLTags aChild) const {
PRBool result=PR_FALSE;
//begin with some simple (and obvious) cases...
switch(aChild) {
case eHTMLTag_userdefined:
case eHTMLTag_comment:
result=PR_TRUE;
break;
case eHTMLTag_html:
case eHTMLTag_body:
result=HasOpenContainer(aChild); //don't bother if they're already open...
break;
case eHTMLTag_button: case eHTMLTag_fieldset:
case eHTMLTag_input: case eHTMLTag_isindex:
case eHTMLTag_label: case eHTMLTag_legend:
case eHTMLTag_select: case eHTMLTag_textarea:
case eHTMLTag_option:
if(PR_FALSE==HasOpenContainer(eHTMLTag_form))
result=PR_TRUE;
break;
case eHTMLTag_newline:
case eHTMLTag_whitespace:
switch(aParent) {
case eHTMLTag_html: case eHTMLTag_head:
case eHTMLTag_title: case eHTMLTag_map:
case eHTMLTag_tr: case eHTMLTag_table:
case eHTMLTag_thead: case eHTMLTag_tfoot:
case eHTMLTag_tbody: case eHTMLTag_col:
case eHTMLTag_colgroup: case eHTMLTag_unknown:
result=PR_TRUE;
default:
break;
} //switch
break;
//this code prevents table container elements from
//opening unless a table is actually already opened.
case eHTMLTag_tr: case eHTMLTag_thead:
case eHTMLTag_tfoot: case eHTMLTag_tbody:
case eHTMLTag_td: case eHTMLTag_th:
case eHTMLTag_caption:
if(PR_FALSE==HasOpenContainer(eHTMLTag_table))
result=PR_TRUE;
break;
case eHTMLTag_entity:
switch(aParent) {
case eHTMLTag_tr: case eHTMLTag_table:
case eHTMLTag_thead: case eHTMLTag_tfoot:
case eHTMLTag_tbody:
result=PR_TRUE;
default:
break;
} //switch
break;
case eHTMLTag_frame:
if(eHTMLTag_iframe==aParent)
result=PR_TRUE;
break;
default:
if(eHTMLTag_unknown==aParent)
result=PR_FALSE;
break;
} //switch
return result;
}
/**
* This method is called when you want to determine if one tag is
* synonymous with another. Cases where this are true include style
* tags (where <i> is allowed to close <b> for example). Another
* is <H?>, where any open heading tag can be closed by any close heading tag.
* @update gess6/16/98
* @param
* @return
*/
PRBool IsCompatibleTag(eHTMLTags aTag1,eHTMLTags aTag2) {
PRBool result=PR_FALSE;
if(0!=strchr(gStyleTags,aTag1)) {
result=PRBool(0!=strchr(gStyleTags,aTag2));
}
if(0!=strchr(gHeadingTags,aTag1)) {
result=PRBool(0!=strchr(gHeadingTags,aTag2));
}
return result;
}
/**
* This method gets called to determine whether a given
* ENDtag can be omitted. Admittedly,this is a gross simplification.
*
* @update gess 3/25/98
* @param aTag -- tag to test for containership
* @return PR_TRUE if given tag can contain other tags
*/
PRBool CNavDTD::CanOmitEndTag(eHTMLTags aParent,eHTMLTags aChild) const {
PRBool result=PR_FALSE;
//begin with some simple (and obvious) cases...
switch((eHTMLTags)aChild) {
case eHTMLTag_userdefined:
case eHTMLTag_comment:
result=PR_TRUE;
break;
case eHTMLTag_html:
case eHTMLTag_body:
result=HasOpenContainer(aChild); //don't bother if they're already open...
break;
case eHTMLTag_newline:
case eHTMLTag_whitespace:
switch(aParent) {
case eHTMLTag_html: case eHTMLTag_head:
case eHTMLTag_title: case eHTMLTag_map:
case eHTMLTag_tr: case eHTMLTag_table:
case eHTMLTag_thead: case eHTMLTag_tfoot:
case eHTMLTag_tbody: case eHTMLTag_col:
case eHTMLTag_colgroup: case eHTMLTag_unknown:
result=PR_TRUE;
default:
break;
} //switch
break;
//It turns out that a <Hn> can be closed by any other <H?>
//This code makes them all seem compatible.
case eHTMLTag_h1: case eHTMLTag_h2:
case eHTMLTag_h3: case eHTMLTag_h4:
case eHTMLTag_h5: case eHTMLTag_h6:
if(0!=strchr(gHeadingTags,aParent)) {
result=PR_FALSE;
break;
}
//Otherwise, IT's OK TO FALL THROUGH HERE...
default:
if(IsGatedFromClosing(aChild))
result=PR_TRUE;
else if(IsCompatibleTag(aChild,GetTopNode()))
result=PR_FALSE;
else result=(!HasOpenContainer(aChild));
break;
} //switch
return result;
}
/**
* This method gets called to determine whether a given
* tag is itself a container
*
* @update gess 4/8/98
* @param aTag -- tag to test for containership
* @return PR_TRUE if given tag can contain other tags
*/
PRBool CNavDTD::IsContainer(eHTMLTags aTag) const {
PRBool result=PR_FALSE;
switch(aTag){
case eHTMLTag_area: case eHTMLTag_base:
case eHTMLTag_basefont: case eHTMLTag_br:
case eHTMLTag_col: case eHTMLTag_colgroup:
case eHTMLTag_embed:
case eHTMLTag_frame:
case eHTMLTag_hr: case eHTMLTag_img:
case eHTMLTag_input: case eHTMLTag_isindex:
case eHTMLTag_link:
case eHTMLTag_meta:
// case eHTMLTag_option:
case eHTMLTag_param:
case eHTMLTag_style:
case eHTMLTag_spacer:
case eHTMLTag_wbr:
case eHTMLTag_form:
case eHTMLTag_newline:
case eHTMLTag_whitespace:
case eHTMLTag_text:
case eHTMLTag_unknown:
result=PR_FALSE;
break;
default:
result=PR_TRUE;
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
eHTMLTags CNavDTD::GetDefaultParentTagFor(eHTMLTags aTag) const{
eHTMLTags result=eHTMLTag_unknown;
switch(aTag) {
case eHTMLTag_text:
result=eHTMLTag_p; break;
case eHTMLTag_html:
result=eHTMLTag_unknown; break;
case eHTMLTag_body:
case eHTMLTag_head:
case eHTMLTag_frameset:
result=eHTMLTag_html; break;
//These tags are head specific...
case eHTMLTag_style:
case eHTMLTag_meta:
case eHTMLTag_title:
case eHTMLTag_base:
case eHTMLTag_link:
result=eHTMLTag_head; break;
//These tags are table specific...
case eHTMLTag_caption:
case eHTMLTag_colgroup:
case eHTMLTag_tbody:
case eHTMLTag_tfoot:
case eHTMLTag_thead:
result=eHTMLTag_table; break;
case eHTMLTag_tr:
result=eHTMLTag_tbody; break;
case eHTMLTag_td:
case eHTMLTag_th:
result=eHTMLTag_tr; break;
case eHTMLTag_col:
result=eHTMLTag_colgroup; break;
case eHTMLTag_dd:
case eHTMLTag_dt:
result=eHTMLTag_dl; break;
case eHTMLTag_option:
result=eHTMLTag_select; break;
//These have to do with image maps...
case eHTMLTag_area:
result=eHTMLTag_map; break;
//These have to do with applets...
case eHTMLTag_param:
result=eHTMLTag_applet; break;
//These have to do with frames...
case eHTMLTag_frame:
result=eHTMLTag_frameset; break;
default:
result=eHTMLTag_body; //XXX Hack! Just for now.
break;
}
return result;
}
/**
* This method tries to design a context vector (without actually
* changing our parser state) from the parent down to the
* child.
*
* @update gess4/6/98
* @param aVector is the string where we store our output vector
* in bottom-up order.
* @param aParent -- tag type of parent
* @param aChild -- tag type of child
* @return TRUE if propagation closes; false otherwise
*/
PRBool CNavDTD::ForwardPropagate(nsString& aVector,eHTMLTags aParentTag,eHTMLTags aChildTag) {
PRBool result=PR_FALSE;
switch(aParentTag) {
case eHTMLTag_table:
{
static char tableTags[]={eHTMLTag_tr,eHTMLTag_td,0};
if(strchr(tableTags,aChildTag)) {
//if you're here, we know we can correctly backward propagate.
return BackwardPropagate(aVector,aParentTag,aChildTag);
}
}
//otherwise, intentionally fall through...
case eHTMLTag_tr:
if(PR_TRUE==CanContain((PRInt32)eHTMLTag_td,(PRInt32)aChildTag)) {
aVector.Append((PRUnichar)eHTMLTag_td);
result=BackwardPropagate(aVector,aParentTag,eHTMLTag_td);
// result=PR_TRUE;
}
break;
case eHTMLTag_th:
break;
default:
break;
}//switch
return result;
}
/**
* This method tries to design a context map (without actually
* changing our parser state) from the child up to the parent.
*
* @update gess4/6/98
* @param aVector is the string where we store our output vector
* in bottom-up order.
* @param aParent -- tag type of parent
* @param aChild -- tag type of child
* @return TRUE if propagation closes; false otherwise
*/
PRBool CNavDTD::BackwardPropagate(nsString& aVector,eHTMLTags aParentTag,eHTMLTags aChildTag) const {
eHTMLTags theParentTag=(eHTMLTags)aChildTag;
// aVector.Truncate();
//create the necessary stack of parent tags...
//continue your search until you run out of known parents,
//or you find the specific parent you were given (aParentTag).
// aVector.Append((PRUnichar)aChildTag);
do {
theParentTag=(eHTMLTags)GetDefaultParentTagFor(theParentTag);
if(theParentTag!=eHTMLTag_unknown) {
aVector.Append((PRUnichar)theParentTag);
}
} while((theParentTag!=eHTMLTag_unknown) && (theParentTag!=aParentTag));
return PRBool(aParentTag==theParentTag);
}
/**
* This method allows the caller to determine if a form
* element is currently open.
*
* @update gess 4/2/98
* @param
* @return
*/
PRBool CNavDTD::HasOpenContainer(eHTMLTags aContainer) const {
PRBool result=PR_FALSE;
switch(aContainer) {
case eHTMLTag_form:
result=mHasOpenForm; break;
default:
result=(kNotFound!=GetTopmostIndexOf(aContainer)); break;
}
return result;
}
/**
* Call this method when you want to determine whether a
* given tag should be prevented (gated) from closing.
*
* @update gess 7/15/96
* @param aTag the tag to be tested for autoclosure
* @return TRUE if tag is gated.
*/
PRBool CNavDTD::IsGatedFromClosing(eHTMLTags aChildTag) const {
PRBool result=PR_FALSE;
PRInt32 tagPos=GetTopmostIndexOf(aChildTag);
PRInt32 theGatePos=kNotFound;
if (0 != strchr(gStyleTags, aChildTag)) {
static char theGateTags[]={
eHTMLTag_caption, eHTMLTag_col, eHTMLTag_colgroup, eHTMLTag_tbody,
eHTMLTag_tfoot, eHTMLTag_tr, eHTMLTag_thead, eHTMLTag_td,
eHTMLTag_body,0};
theGatePos=GetTopmostIndexOf(theGateTags);
}
else {
switch(aChildTag) {
case eHTMLTag_li:
{
static char theGateTags[]={eHTMLTag_ol,eHTMLTag_ul,0};
theGatePos=GetTopmostIndexOf(theGateTags);
}
break;
case eHTMLTag_td:
case eHTMLTag_tr:
theGatePos=GetTopmostIndexOf(gTableTags);
break;
/*
eHTMLTag_table
eHTMLTag_tbody
eHTMLTag_thead
eHTMLTag_tfoot
eHTMLTag_caption
eHTMLTag_col
eHTMLTag_colgroup
*/
default:
break;
}
}
if(kNotFound!=theGatePos)
if(kNotFound!=tagPos)
result=PRBool(theGatePos>tagPos);
return result;
}
/**
* This method retrieves the HTMLTag type of the topmost
* container on the stack.
*
* @update gess 4/2/98
* @return tag id of topmost node in contextstack
*/
eHTMLTags CNavDTD::GetTopNode() const {
return mContextStack.Last();
}
/**
* Determine whether the given tag is open anywhere
* in our context stack.
*
* @update gess 4/2/98
* @param eHTMLTags tag to be searched for in stack
* @return topmost index of tag on stack
*/
PRInt32 CNavDTD::GetTopmostIndexOf(char aTagSet[]) const {
int i=0;
for(i=mContextStack.mCount-1;i>=0;i--){
if(0!=strchr(aTagSet,mContextStack.mTags[i]))
return i;
}
return kNotFound;
}
/**
* Determine whether the given tag is open anywhere
* in our context stack.
*
* @update gess 4/2/98
* @param eHTMLTags tag to be searched for in stack
* @return topmost index of tag on stack
*/
PRInt32 CNavDTD::GetTopmostIndexOf(eHTMLTags aTag) const {
int i=0;
for(i=mContextStack.mCount-1;i>=0;i--){
if(mContextStack.mTags[i]==aTag)
return i;
}
return kNotFound;
}
/*********************************************
Here comes code that handles the interface
to our content sink.
*********************************************/
/**
* It is with great trepidation that I offer this method (privately of course).
* The gets called whenever a container gets opened. This methods job is to
* take a look at the (transient) style stack, and open any style containers that
* are there. Of course, we shouldn't bother to open styles that are incompatible
* with our parent container.
*
* @update gess6/4/98
* @param tag of the container just opened
* @return 0 (for now)
*/
nsresult CNavDTD::OpenTransientStyles(eHTMLTags aTag){
nsresult result=NS_OK;
if(0==strchr(gWhitespaceTags,aTag)){
PRInt32 pos=0;
eHTMLTags parentTag=GetTopNode();
if(CanContainStyles(parentTag)) {
for(pos=0;pos<mStyleStack->mCount;pos++) {
eHTMLTags theTag=mStyleStack->mTags[pos];
if(PR_FALSE==HasOpenContainer(theTag)) {
CStartToken token(theTag);
nsCParserNode theNode(&token,mLineNumber);
switch(theTag) {
case eHTMLTag_secret_h1style: case eHTMLTag_secret_h2style:
case eHTMLTag_secret_h3style: case eHTMLTag_secret_h4style:
case eHTMLTag_secret_h5style: case eHTMLTag_secret_h6style:
break;
default:
token.SetTypeID(theTag); //open the html container...
result=OpenContainer(theNode,PR_FALSE);
mContextStack.mBits[mContextStack.mCount-1]=PR_TRUE;
} //switch
}
if(NS_OK!=result)
break;
}//for
}
}//if
return result;
}
/**
* It is with great trepidation that I offer this method (privately of course).
* The gets called just prior when a container gets opened. This methods job is to
* take a look at the (transient) style stack, and <i>close</i> any style containers
* that are there. Of course, we shouldn't bother to open styles that are incompatible
* with our parent container.
* SEE THE TOP OF THIS FILE for more information about how the transient style stack works.
*
* @update gess6/4/98
* @param tag of the container just opened
* @return 0 (for now)
*/
nsresult CNavDTD::CloseTransientStyles(eHTMLTags aTag){
nsresult result=NS_OK;
if((mStyleStack->mCount>0) && (mContextStack.mBits[mContextStack.mCount-1])) {
if(0==strchr(gWhitespaceTags,aTag)){
result=CloseContainersTo(mStyleStack->mTags[0],PR_FALSE);
mContextStack.mBits[mContextStack.mCount-1]=PR_FALSE;
}//if
}//if
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
*
* @update gess4/22/98
* @param aNode -- next node to be added to model
*/
nsresult CNavDTD::OpenHTML(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount >= 0, kInvalidTagStackPos);
nsresult result=mSink->OpenHTML(aNode);
mContextStack.Push((eHTMLTags)aNode.GetNodeType());
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
*
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseHTML(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
nsresult result=mSink->CloseHTML(aNode);
mContextStack.Pop();
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::OpenHead(const nsIParserNode& aNode){
mContextStack.Push(eHTMLTag_head);
nsresult result=mSink->OpenHead(aNode);
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseHead(const nsIParserNode& aNode){
nsresult result=mSink->CloseHead(aNode);
mContextStack.Pop();
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::OpenBody(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount >= 0, kInvalidTagStackPos);
nsresult result=NS_OK;
eHTMLTags topTag=GetTopNode();
if(eHTMLTag_html!=topTag) {
//ok, there are two cases:
// 1. Nobody opened the html container
// 2. Someone left the head (or other) open
PRInt32 pos=GetTopmostIndexOf(eHTMLTag_html);
if(kNotFound!=pos) {
//if you're here, it means html is open,
//but some other tag(s) are in the way.
//So close other tag(s).
result=CloseContainersTo(pos+1,eHTMLTag_body,PR_TRUE);
} else {
//if you're here, it means that there is
//no HTML tag in document. Let's open it.
result=CloseContainersTo(0,eHTMLTag_html,PR_TRUE); //close current stack containers.
CHTMLToken token(gEmpty);
nsCParserNode htmlNode(&token,mLineNumber);
token.SetTypeID(eHTMLTag_html); //open the html container...
result=OpenHTML(htmlNode);
}
}
if(NS_OK==result) {
result=mSink->OpenBody(aNode);
mContextStack.Push((eHTMLTags)aNode.GetNodeType());
}
return result;
}
/**
* This method does two things: 1st, help close
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseBody(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount >= 0, kInvalidTagStackPos);
nsresult result=mSink->CloseBody(aNode);
mContextStack.Pop();
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::OpenForm(const nsIParserNode& aNode){
if(mHasOpenForm)
CloseForm(aNode);
nsresult result=mSink->OpenForm(aNode);
if(NS_OK==result)
mHasOpenForm=PR_TRUE;
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseForm(const nsIParserNode& aNode){
nsresult result=NS_OK;
if(mHasOpenForm) {
mHasOpenForm=PR_FALSE;
result=mSink->CloseForm(aNode);
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::OpenMap(const nsIParserNode& aNode){
if(mHasOpenMap)
CloseMap(aNode);
nsresult result=mSink->OpenMap(aNode);
if(NS_OK==result)
mHasOpenMap=PR_TRUE;
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseMap(const nsIParserNode& aNode){
nsresult result=NS_OK;
if(mHasOpenMap) {
mHasOpenMap=PR_FALSE;
result=mSink->CloseMap(aNode);
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::OpenFrameset(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount >= 0, kInvalidTagStackPos);
nsresult result=mSink->OpenFrameset(aNode);
mContextStack.Push((eHTMLTags)aNode.GetNodeType());
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseFrameset(const nsIParserNode& aNode){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
nsresult result=mSink->CloseFrameset(aNode);
mContextStack.Pop();
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult
CNavDTD::OpenContainer(const nsIParserNode& aNode,PRBool aUpdateStyleStack){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
nsresult result=NS_OK;
eHTMLTags nodeType=(eHTMLTags)aNode.GetNodeType();
switch(nodeType) {
case eHTMLTag_html:
result=OpenHTML(aNode); break;
case eHTMLTag_body:
result=OpenBody(aNode); break;
case eHTMLTag_style:
case eHTMLTag_textarea:
case eHTMLTag_head:
case eHTMLTag_title:
break;
case eHTMLTag_map:
result=OpenMap(aNode);
break;
case eHTMLTag_form:
result=OpenForm(aNode); break;
default:
result=mSink->OpenContainer(aNode);
mContextStack.Push((eHTMLTags)aNode.GetNodeType());
break;
}
if((NS_OK==result) && (PR_TRUE==aUpdateStyleStack)){
UpdateStyleStackForOpenTag(nodeType,nodeType);
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be removed from our model
* @return TRUE if ok, FALSE if error
*/
nsresult
CNavDTD::CloseContainer(const nsIParserNode& aNode,eHTMLTags aTag,
PRBool aUpdateStyles){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
nsresult result=NS_OK;
eHTMLTags nodeType=(eHTMLTags)aNode.GetNodeType();
//XXX Hack! We know this is wrong, but it works
//for the general case until we get it right.
switch(nodeType) {
case eHTMLTag_html:
result=CloseHTML(aNode); break;
case eHTMLTag_style:
case eHTMLTag_textarea:
break;
case eHTMLTag_head:
//result=CloseHead(aNode);
break;
case eHTMLTag_body:
result=CloseBody(aNode); break;
case eHTMLTag_map:
result=CloseMap(aNode);
break;
case eHTMLTag_form:
result=CloseForm(aNode); break;
case eHTMLTag_title:
default:
result=mSink->CloseContainer(aNode);
mContextStack.Pop();
break;
}
mContextStack.mBits[mContextStack.mCount]=PR_FALSE;
if((NS_OK==result) && (PR_TRUE==aUpdateStyles)){
UpdateStyleStackForCloseTag(nodeType,aTag);
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param
* @return TRUE if ok, FALSE if error
*/
nsresult
CNavDTD::CloseContainersTo(PRInt32 anIndex,eHTMLTags aTag,
PRBool aUpdateStyles){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
nsresult result=NS_OK;
CEndToken aToken(gEmpty);
nsCParserNode theNode(&aToken,mLineNumber);
if((anIndex<mContextStack.mCount) && (anIndex>=0)) {
while(mContextStack.mCount>anIndex) {
eHTMLTags theTag=mContextStack.Last();
aToken.SetTypeID(theTag);
result=CloseContainer(theNode,aTag,aUpdateStyles);
}
}
return result;
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseContainersTo(eHTMLTags aTag,PRBool aUpdateStyles){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
PRInt32 pos=GetTopmostIndexOf(aTag);
if(kNotFound!=pos) {
//the tag is indeed open, so close it.
return CloseContainersTo(pos,aTag,aUpdateStyles);
}
eHTMLTags theTopTag=GetTopNode();
if(IsCompatibleTag(aTag,theTopTag)) {
//if you're here, it's because we're trying to close one style tag,
//but a different one is actually open. Because this is NAV4x
//compatibililty mode, we must close the one that's really open.
aTag=theTopTag;
pos=GetTopmostIndexOf(aTag);
if(kNotFound!=pos) {
//the tag is indeed open, so close it.
return CloseContainersTo(pos,aTag,aUpdateStyles);
}
}
nsresult result=NS_OK;
eHTMLTags theParentTag=GetDefaultParentTagFor(aTag);
pos=GetTopmostIndexOf(theParentTag);
if(kNotFound!=pos) {
//the parent container is open, so close it instead
result=CloseContainersTo(pos+1,aTag,aUpdateStyles);
}
return result;
}
/**
* This method causes the topmost container on the stack
* to be closed.
* @update gess4/6/98
* @see CloseContainer()
* @param
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::CloseTopmostContainer(){
NS_PRECONDITION(mContextStack.mCount > 0, kInvalidTagStackPos);
CEndToken aToken(gEmpty);
eHTMLTags theTag=mContextStack.Last();
aToken.SetTypeID(theTag);
nsCParserNode theNode(&aToken,mLineNumber);
return CloseContainer(theNode,theTag,PR_TRUE);
}
/**
* This method does two things: 1st, help construct
* our own internal model of the content-stack; and
* 2nd, pass this message on to the sink.
* @update gess4/6/98
* @param aNode -- next node to be added to model
* @return TRUE if ok, FALSE if error
*/
nsresult CNavDTD::AddLeaf(const nsIParserNode& aNode){
nsresult result=mSink->AddLeaf(aNode);
return result;
}
/**
* This method gets called to create a valid context stack
* for the given child. We compare the current stack to the
* default needs of the child, and push new guys onto the
* stack until the child can be properly placed.
*
* @update gess 4/8/98
* @param aChildTag is the child for whom we need to
* create a new context vector
* @return true if we succeeded, otherwise false
*/
nsresult CNavDTD::CreateContextStackFor(eHTMLTags aChildTag){
nsAutoString theVector;
nsresult result=NS_OK;
PRInt32 pos=0;
PRInt32 cnt=0;
eHTMLTags theTop=GetTopNode();
if(PR_TRUE==ForwardPropagate(theVector,theTop,aChildTag)){
//add code here to build up context stack based on forward propagated context vector...
pos=0;
cnt=theVector.Length()-1;
if(mContextStack.Last()==(eHTMLTags)theVector[cnt])
result=NS_OK;
else result=(nsresult)kContextMismatch;
}
else {
PRBool tempResult;
if(eHTMLTag_unknown!=theTop) {
tempResult=BackwardPropagate(theVector,theTop,aChildTag);
if(eHTMLTag_html!=theTop)
BackwardPropagate(theVector,eHTMLTag_html,theTop);
}
else tempResult=BackwardPropagate(theVector,eHTMLTag_html,aChildTag);
if(PR_TRUE==tempResult) {
//propagation worked, so pop unwanted containers, push new ones, then exit...
pos=0;
cnt=theVector.Length();
result=NS_OK;
while(pos<mContextStack.mCount) {
if(mContextStack.mTags[pos]==(eHTMLTags)theVector[cnt-1-pos]) {
pos++;
}
else {
//if you're here, you have something on the stack
//that doesn't match your needed tags order.
result=CloseContainersTo(pos,eHTMLTag_unknown,PR_TRUE);
break;
}
} //while
} //elseif
else result=(nsresult)kCantPropagate;
} //elseif
//now, build up the stack according to the tags
//you have that aren't in the stack...
if(NS_OK==result){
int i=0;
for(i=pos;i<cnt;i++) {
// CStartToken* st=new CStartToken((eHTMLTags)theVector[cnt-1-i]);
CToken* theToken=gTokenRecycler.CreateTokenOfType(eToken_start,(eHTMLTags)theVector[cnt-1-i],gEmpty);
HandleStartToken(theToken);
}
}
return result;
}
/**
* This method gets called to ensure that the context
* stack is properly set up for the given child.
* We pop containers off the stack (all the way down
* html) until we get a container that can contain
* the given child.
*
* @update gess 4/8/98
* @param
* @return
*/
nsresult CNavDTD::ReduceContextStackFor(eHTMLTags aChildTag){
nsresult result=NS_OK;
eHTMLTags topTag=GetTopNode();
while( (topTag!=kNotFound) &&
(PR_FALSE==CanContain(topTag,aChildTag)) &&
(PR_FALSE==CanContainIndirect(topTag,aChildTag))) {
CloseTopmostContainer();
topTag=GetTopNode();
}
return result;
}
/**
* This method causes all explicit style-tag containers that
* are opened to be reflected on our internal style-stack.
*
* @update gess6/4/98
* @param aTag is the id of the html container being opened
* @return 0 if all is well.
*/
nsresult
CNavDTD::UpdateStyleStackForOpenTag(eHTMLTags aTag,eHTMLTags anActualTag){
nsresult result=0;
if (0 != strchr(gStyleTags, aTag)) {
mStyleStack->Push(aTag);
}
else {
switch (aTag) {
case eHTMLTag_h1: case eHTMLTag_h2:
case eHTMLTag_h3: case eHTMLTag_h4:
case eHTMLTag_h5: case eHTMLTag_h6:
break;
default:
break;
}
}
return result;
} //update...
/**
* This method gets called when an explicit style close-tag is encountered.
* It results in the style tag id being popped from our internal style stack.
*
* @update gess6/4/98
* @param
* @return 0 if all went well (which it always does)
*/
nsresult
CNavDTD::UpdateStyleStackForCloseTag(eHTMLTags aTag,eHTMLTags anActualTag){
nsresult result=0;
if(mStyleStack->mCount>0) {
if (0 != strchr(gStyleTags, aTag)) {
if(aTag==anActualTag)
mStyleStack->Pop();
}
else {
switch (aTag) {
case eHTMLTag_h1: case eHTMLTag_h2:
case eHTMLTag_h3: case eHTMLTag_h4:
case eHTMLTag_h5: case eHTMLTag_h6:
break;
default:
break;
}//switch
}
}//if
return result;
}
/*******************************************************************
These methods used to be hidden in the tokenizer-delegate.
That file merged with the DTD, since the separation wasn't really
buying us anything.
*******************************************************************/
/**
* This method is called just after a "<" has been consumed
* and we know we're at the start of some kind of tagged
* element. We don't know yet if it's a tag or a comment.
*
* @update gess 5/12/98
* @param aChar is the last char read
* @param aScanner is represents our input source
* @param aToken is the out arg holding our new token
* @return error code (may return kInterrupted).
*/
nsresult
CNavDTD::ConsumeTag(PRUnichar aChar,CScanner& aScanner,CToken*& aToken) {
nsresult result=aScanner.GetChar(aChar);
if(NS_OK==result) {
switch(aChar) {
case kForwardSlash:
PRUnichar ch;
result=aScanner.Peek(ch);
if(NS_OK==result) {
if(nsString::IsAlpha(ch))
aToken=gTokenRecycler.CreateTokenOfType(eToken_end,eHTMLTag_unknown,gEmpty);
else aToken=gTokenRecycler.CreateTokenOfType(eToken_comment,eHTMLTag_unknown,gEmpty);
}//if
break;
case kExclamation:
aToken=gTokenRecycler.CreateTokenOfType(eToken_comment,eHTMLTag_unknown,gEmpty);
break;
default:
if(nsString::IsAlpha(aChar))
return ConsumeStartTag(aChar,aScanner,aToken);
else if(kEOF!=aChar) {
nsAutoString temp("<");
return ConsumeText(temp,aScanner,aToken);
}
} //switch
if((0!=aToken) && (NS_OK==result)) {
result= aToken->Consume(aChar,aScanner); //tell new token to finish consuming text...
if(result) {
delete aToken;
aToken=0;
}
} //if
} //if
return result;
}
/**
* This method is called just after we've consumed a start
* tag, and we now have to consume its attributes.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @return
*/
nsresult
CNavDTD::ConsumeAttributes(PRUnichar aChar,CScanner& aScanner,CStartToken* aToken) {
PRBool done=PR_FALSE;
nsresult result=NS_OK;
PRInt16 theAttrCount=0;
while((!done) && (result==NS_OK)) {
CAttributeToken* theToken= (CAttributeToken*)gTokenRecycler.CreateTokenOfType(eToken_attribute,eHTMLTag_unknown,gEmpty);
if(theToken){
result=theToken->Consume(aChar,aScanner); //tell new token to finish consuming text...
//Much as I hate to do this, here's some special case code.
//This handles the case of empty-tags in XML. Our last
//attribute token will come through with a text value of ""
//and a textkey of "/". We should destroy it, and tell the
//start token it was empty.
nsString& key=theToken->GetKey();
nsString& text=theToken->GetStringValueXXX();
if((key[0]==kForwardSlash) && (0==text.Length())){
//tada! our special case! Treat it like an empty start tag...
aToken->SetEmpty(PR_TRUE);
delete theToken;
}
else if(NS_OK==result){
theAttrCount++;
mTokenDeque.Push(theToken);
}//if
else delete theToken; //we can't keep it...
}//if
if(NS_OK==result){
result=aScanner.Peek(aChar);
if(aChar==kGreaterThan) { //you just ate the '>'
aScanner.GetChar(aChar); //skip the '>'
done=PR_TRUE;
}//if
}//if
}//while
aToken->SetAttributeCount(theAttrCount);
return result;
}
/**
* This is a special case method. It's job is to consume
* all of the given tag up to an including the end tag.
*
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult
CNavDTD::ConsumeContentToEndTag(const nsString& aString,
PRUnichar aChar,
eHTMLTags aChildTag,
CScanner& aScanner,
CToken*& aToken){
//In the case that we just read the given tag, we should go and
//consume all the input until we find a matching end tag.
nsAutoString endTag("</");
endTag.Append(aString);
endTag.Append(">");
aToken=gTokenRecycler.CreateTokenOfType(eToken_skippedcontent,aChildTag,endTag);
return aToken->Consume(aChar,aScanner); //tell new token to finish consuming text...
}
/**
* This method is called just after a "<" has been consumed
* and we know we're at the start of a tag.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
static char gSkippedContentTags[]={ eHTMLTag_script, eHTMLTag_style, eHTMLTag_title, eHTMLTag_textarea, 0};
nsresult
CNavDTD::ConsumeStartTag(PRUnichar aChar,CScanner& aScanner,CToken*& aToken) {
PRInt32 theDequeSize=mTokenDeque.GetSize();
nsresult result=NS_OK;
aToken=gTokenRecycler.CreateTokenOfType(eToken_start,eHTMLTag_unknown,gEmpty);
if(aToken) {
result= aToken->Consume(aChar,aScanner); //tell new token to finish consuming text...
if(NS_OK==result) {
if(((CStartToken*)aToken)->IsAttributed()) {
result=ConsumeAttributes(aChar,aScanner,(CStartToken*)aToken);
}
//now that that's over with, we have one more problem to solve.
//In the case that we just read a <SCRIPT> or <STYLE> tags, we should go and
//consume all the content itself.
if(NS_OK==result) {
eHTMLTags theTag=(eHTMLTags)aToken->GetTypeID();
if(0!=strchr(gSkippedContentTags,theTag)){
//Do special case handling for <script>, <style>, <title> or <textarea>...
CToken* skippedToken=0;
nsString& str=aToken->GetStringValueXXX();
result=ConsumeContentToEndTag(str,aChar,theTag,aScanner,skippedToken);
if((NS_OK==result) && skippedToken){
//now we strip the ending sequence from our new SkippedContent token...
PRInt32 slen=str.Length()+3;
nsString& skippedText=skippedToken->GetStringValueXXX();
skippedText.Cut(skippedText.Length()-slen,slen);
mTokenDeque.Push(skippedToken);
//In the case that we just read a given tag, we should go and
//consume all the tag content itself (and throw it all away).
CToken* endtoken=gTokenRecycler.CreateTokenOfType(eToken_end,theTag,gEmpty);
mTokenDeque.Push(endtoken);
} //if
} //if
} //if
//EEEEECCCCKKKK!!!
//This code is confusing, so pay attention.
//If you're here, it's because we were in the midst of consuming a start
//tag but ran out of data (not in the stream, but in this *part* of the stream.
//For simplicity, we have to unwind our input. Therefore, we pop and discard
//any new tokens we've cued this round. Later we can get smarter about this.
if(NS_OK!=result) {
while(mTokenDeque.GetSize()>theDequeSize) {
delete mTokenDeque.PopBack();
}
}
} //if
} //if
return result;
}
/**
* This method is called just after a "&" has been consumed
* and we know we're at the start of an entity.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult
CNavDTD::ConsumeEntity(PRUnichar aChar,CScanner& aScanner,CToken*& aToken) {
PRUnichar ch;
nsresult result=aScanner.GetChar(ch);
if(NS_OK==result) {
if(nsString::IsAlpha(ch)) { //handle common enity references &xxx; or &#000.
aToken = gTokenRecycler.CreateTokenOfType(eToken_entity,eHTMLTag_unknown,gEmpty);
result = aToken->Consume(ch,aScanner); //tell new token to finish consuming text...
}
else if(kHashsign==ch) {
aToken = gTokenRecycler.CreateTokenOfType(eToken_entity,eHTMLTag_unknown,gEmpty);
result=aToken->Consume(0,aScanner);
}
else {
//oops, we're actually looking at plain text...
nsAutoString temp("&");
temp.Append(ch);
result=ConsumeText(temp,aScanner,aToken);
}
}//if
return result;
}
/**
* This method is called just after whitespace has been
* consumed and we know we're at the start a whitespace run.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult
CNavDTD::ConsumeWhitespace(PRUnichar aChar,
CScanner& aScanner,
CToken*& aToken) {
aToken = gTokenRecycler.CreateTokenOfType(eToken_whitespace,eHTMLTag_unknown,gEmpty);
nsresult result=kNoError;
if(aToken) {
result=aToken->Consume(aChar,aScanner);
}
return kNoError;
}
/**
* This method is called just after a "<!" has been consumed
* and we know we're at the start of a comment.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult CNavDTD::ConsumeComment(PRUnichar aChar,CScanner& aScanner,CToken*& aToken){
aToken = gTokenRecycler.CreateTokenOfType(eToken_comment,eHTMLTag_unknown,gEmpty);
nsresult result=NS_OK;
if(aToken) {
result=aToken->Consume(aChar,aScanner);
}
return result;
}
/**
* This method is called just after a known text char has
* been consumed and we should read a text run.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult CNavDTD::ConsumeText(const nsString& aString,CScanner& aScanner,CToken*& aToken){
nsresult result=NS_OK;
aToken=gTokenRecycler.CreateTokenOfType(eToken_text,eHTMLTag_text,aString);
if(aToken) {
PRUnichar ch=0;
result=aToken->Consume(ch,aScanner);
if(result) {
nsString& temp=aToken->GetStringValueXXX();
if(0==temp.Length()){
delete aToken;
}
else result=kNoError;
}
}
return result;
}
/**
* This method is called just after a newline has been consumed.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param aToken is the newly created newline token that is parsing
* @return error code
*/
nsresult CNavDTD::ConsumeNewline(PRUnichar aChar,CScanner& aScanner,CToken*& aToken){
aToken=gTokenRecycler.CreateTokenOfType(eToken_newline,eHTMLTag_newline,gEmpty);
nsresult result=NS_OK;
if(aToken) {
result=aToken->Consume(aChar,aScanner);
}
return kNoError;
}
/**
* This method repeatedly called by the tokenizer.
* Each time, we determine the kind of token were about to
* read, and then we call the appropriate method to handle
* that token type.
*
* @update gess 3/25/98
* @param aChar: last char read
* @param aScanner: see nsScanner.h
* @param anErrorCode: arg that will hold error condition
* @return new token or null
*/
nsresult CNavDTD::ConsumeToken(CToken*& aToken){
aToken=0;
if(mTokenDeque.GetSize()>0) {
aToken=(CToken*)mTokenDeque.Pop();
return NS_OK;
}
nsresult result=NS_OK;
CScanner* theScanner=mParser->GetScanner();
if(NS_OK==result){
PRUnichar theChar;
result=theScanner->GetChar(theChar);
switch(result) {
case kEOF:
//We convert from eof to complete here, because we never really tried to get data.
//All we did was try to see if data was available, which it wasn't.
//It's important to return process complete, so that controlling logic can know that
//everything went well, but we're done with token processing.
result=kProcessComplete;
break;
case kInterrupted:
theScanner->RewindToMark();
break;
case NS_OK:
default:
switch(theChar) {
case kLessThan:
result=ConsumeTag(theChar,*theScanner,aToken);
break;
case kAmpersand:
result=ConsumeEntity(theChar,*theScanner,aToken);
break;
case kCR: case kLF:
result=ConsumeNewline(theChar,*theScanner,aToken);
break;
case kNotFound:
break;
default:
if(!nsString::IsSpace(theChar)) {
nsAutoString temp(theChar);
result=ConsumeText(temp,*theScanner,aToken);
break;
}
result=ConsumeWhitespace(theChar,*theScanner,aToken);
break;
} //switch
break;
} //switch
// if(NS_OK==result)
// result=theScanner->Eof();
} //if
return result;
}
/**
*
* @update gess5/18/98
* @param
* @return
*/
nsresult CNavDTD::WillResumeParse(void){
nsresult result = NS_OK;
if(mSink) {
result = mSink->WillResume();
}
return result;
}
/**
* This method gets called when the parsing process is interrupted
* due to lack of data (waiting for netlib).
* @update gess5/18/98
* @return error code
*/
nsresult CNavDTD::WillInterruptParse(void){
nsresult result = NS_OK;
if(mSink) {
result = mSink->WillInterrupt();
}
return result;
}