/* -*- 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. */ /** * MODULE NOTES: * @update gess 4/8/98 * * */ /** * TRANSIENT STYLE-HANDLING NOTES: * @update gess 6/15/98 * * ...add comments here about transient style stack. * */ #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 "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 //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* kNullTokenizer = "Error: Unable to construct tokenizer"; static const char* kNullToken = "Error: Null token given"; static const char* kInvalidTagStackPos = "Error: invalid tag stack position"; static const char* kHTMLTextContentType = "text/html"; 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_b, eHTMLTag_big, eHTMLTag_blink, eHTMLTag_center, eHTMLTag_cite, eHTMLTag_em, eHTMLTag_font, eHTMLTag_i, eHTMLTag_kbd, eHTMLTag_s, eHTMLTag_small, 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; nsCRT::zero(mTags,mSize*sizeof(eHTMLTag_html)); 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; } /************************************************************************ 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(), mTokenDeque(gTokenKiller), mContextStack(), mStyleStack() { NS_INIT_REFCNT(); mParser=0; mSink = nsnull; mDTDDebug=0; nsCRT::zero(mTokenHandlers,sizeof(mTokenHandlers)); mHasOpenForm=PR_FALSE; mHasOpenMap=PR_FALSE; InitializeDefaultTokenHandlers(); } /** * Default destructor * * @update gess 4/9/98 * @param * @return */ CNavDTD::~CNavDTD(){ DeleteTokenHandlers(); if (mDTDDebug) NS_RELEASE(mDTDDebug); // NS_RELEASE(mSink); } /** * * @update jevering6/23/98 * @param * @return */ void CNavDTD::SetDTDDebug(nsIDTDDebug * aDTDDebug) { if (mDTDDebug) NS_RELEASE(mDTDDebug); mDTDDebug = aDTDDebug; if (mDTDDebug) NS_ADDREF(mDTDDebug); } /** * 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){ nsresult result=NS_OK; mFilename=aFilename; if(mSink) { result = mSink->WillBuildModel(); } return result; } /** * * @update gess5/18/98 * @param * @return */ nsresult CNavDTD::DidBuildModel(PRInt32 anErrorCode){ nsresult result= NS_OK; if((kNoError==anErrorCode) && (mContextStack.mCount>0)) { result = CloseContainersTo(0,eHTMLTag_unknown,PR_FALSE); } if(mSink) { result = mSink->DidBuildModel(1); } 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); PRInt16 attrCount=aToken->GetAttributeCount(); PRInt32 theCount; nsresult result=(0==attrCount) ? NS_OK : mParser->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); result=OpenHead(theNode); //open the head... if(NS_OK==result) { result=mParser->CollectSkippedContent(attrNode,theCount); mSink->SetTitle(attrNode.GetSkippedContent()); result=CloseHead(theNode); //close the head... } } break; case eHTMLTag_textarea: { mParser->CollectSkippedContent(attrNode,theCount); result=AddLeaf(attrNode); } break; case eHTMLTag_form: result = OpenForm(attrNode); break; case eHTMLTag_meta: case eHTMLTag_link: { nsCParserNode theNode((CHTMLToken*)aToken); 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); result=OpenHead(theNode); if(NS_OK==result) { mParser->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_nobr: result=PR_TRUE; case eHTMLTag_map: result=PR_TRUE; default: result=HandleDefaultStartToken(aToken,tokenTagType,attrNode); break; } //switch } //if } //if 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); 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: result=CloseContainer(theNode,tokenTagType,PR_TRUE); break; case eHTMLTag_form: { nsCParserNode aNode((CHTMLToken*)aToken); result=CloseForm(aNode); } 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); 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); 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!"); CAttributeToken* at = (CAttributeToken*)(aToken); nsresult result=NS_OK; return result; } /** * 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); // nsCParserNode theNode((CHTMLToken*)aToken); // nsCParserNode attrNode((CHTMLToken*)aToken); PRInt32 attrCount=aToken->GetAttributeCount(); if (kNotFound == pos) { // We're in the HEAD result=OpenHead(aNode); if(NS_OK==result) { mParser->CollectSkippedContent(aNode,attrCount); if(NS_OK==result) { result=AddLeaf(aNode); if(NS_OK==result) result=CloseHead(aNode); } } } else { // We're in the BODY mParser->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); nsresult result=NS_OK; 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;i0) && (aTypeGetTokenType(); if(typetagPos); 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;posclose 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); 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(); // CloseTransientStyles(nodeType); 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); if((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(IsCompatibleStyleTag(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); 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=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(pos0) { switch (aTag) { case eHTMLTag_a: case eHTMLTag_b: case eHTMLTag_big: case eHTMLTag_blink: case eHTMLTag_cite: case eHTMLTag_em: case eHTMLTag_font: case eHTMLTag_i: case eHTMLTag_kbd: case eHTMLTag_small: case eHTMLTag_s: case eHTMLTag_strike: case eHTMLTag_strong: case eHTMLTag_sub: case eHTMLTag_sup: case eHTMLTag_tt: case eHTMLTag_u: case eHTMLTag_var: if(aTag==anActualTag) mStyleStack.Pop(); break; 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; } //update... /******************************************************************* 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) { nsAutoString empty(""); 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=new CEndToken(empty); else aToken=new CCommentToken(empty); //Special case: is treated as a comment }//if break; case kExclamation: aToken=new CCommentToken(empty); 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; nsAutoString as(""); PRInt16 theAttrCount=0; while((!done) && (result==NS_OK)) { CAttributeToken* theToken= new CAttributeToken(as); 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->GetText(); 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, 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(""); aToken=new CSkippedContentToken(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 */ nsresult CNavDTD::ConsumeStartTag(PRUnichar aChar,CScanner& aScanner,CToken*& aToken) { PRInt32 theDequeSize=mTokenDeque.GetSize(); nsresult result=NS_OK; aToken=new CStartToken(nsAutoString("")); 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