1174 lines
32 KiB
C++
1174 lines
32 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.1 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS
|
|
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* rights and limitations under the License.
|
|
*
|
|
* The Original Code is mozilla.org code.
|
|
*
|
|
* The Initial Developer of the Original Code is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*/
|
|
|
|
//#define ENABLE_CRC
|
|
//#define RICKG_DEBUG
|
|
|
|
|
|
#include "nsDebug.h"
|
|
#include "nsIDTDDebug.h"
|
|
#include "COtherDTD.h"
|
|
#include "nsHTMLTokens.h"
|
|
#include "nsCRT.h"
|
|
#include "nsParser.h"
|
|
#include "nsIParser.h"
|
|
#include "nsIHTMLContentSink.h"
|
|
#include "nsScanner.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"
|
|
#include "nsDTDUtils.h"
|
|
#include "nsTagHandler.h"
|
|
#include "nsHTMLTokenizer.h"
|
|
#include "nsTime.h"
|
|
#include "nsIElementObserver.h"
|
|
#include "nsViewSourceHTML.h"
|
|
#include "nsParserNode.h"
|
|
#include "nsHTMLEntities.h"
|
|
#include "nsLinebreakConverter.h"
|
|
|
|
#include "prmem.h"
|
|
|
|
|
|
static NS_DEFINE_IID(kIHTMLContentSinkIID, NS_IHTML_CONTENT_SINK_IID);
|
|
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
|
|
static NS_DEFINE_IID(kIDTDIID, NS_IDTD_IID);
|
|
static NS_DEFINE_IID(kClassIID, NS_IOTHERHTML_DTD_IID);
|
|
|
|
static const char* kNullToken = "Error: Null token given";
|
|
static const char* kInvalidTagStackPos = "Error: invalid tag stack position";
|
|
static char* kVerificationDir = "c:/temp";
|
|
|
|
|
|
#ifdef ENABLE_CRC
|
|
static char gShowCRC;
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef MOZ_PERF_METRICS
|
|
# define START_TIMER() \
|
|
if(mParser) MOZ_TIMER_START(mParser->mParseTime); \
|
|
if(mParser) MOZ_TIMER_START(mParser->mDTDTime);
|
|
|
|
# define STOP_TIMER() \
|
|
if(mParser) MOZ_TIMER_STOP(mParser->mParseTime); \
|
|
if(mParser) MOZ_TIMER_STOP(mParser->mDTDTime);
|
|
#else
|
|
# define STOP_TIMER()
|
|
# define START_TIMER()
|
|
#endif
|
|
|
|
|
|
#include "COtherElements.h"
|
|
|
|
|
|
/************************************************************************
|
|
And now for the main class -- COtherDTD...
|
|
************************************************************************/
|
|
|
|
/**
|
|
* 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 COtherDTD::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 = (COtherDTD*)(this);
|
|
}
|
|
else {
|
|
*aInstancePtr=0;
|
|
return NS_NOINTERFACE;
|
|
}
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ADDREF(COtherDTD)
|
|
NS_IMPL_RELEASE(COtherDTD)
|
|
|
|
/**
|
|
* Default constructor
|
|
*
|
|
* @update gess 4/9/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
COtherDTD::COtherDTD() : nsIDTD(), mSharedNodes(0) {
|
|
NS_INIT_REFCNT();
|
|
mSink = 0;
|
|
mParser=0;
|
|
mDTDDebug=0;
|
|
mLineNumber=1;
|
|
mHasOpenBody=PR_FALSE;
|
|
mHasOpenHead=0;
|
|
mHasOpenForm=PR_FALSE;
|
|
mHasOpenMap=PR_FALSE;
|
|
mBodyContext=new nsDTDContext();
|
|
mTokenizer=0;
|
|
mComputedCRC32=0;
|
|
mExpectedCRC32=0;
|
|
mDTDState=NS_OK;
|
|
mDocType=eHTML4Text;
|
|
mHadFrameset=PR_FALSE;
|
|
mHadBody=PR_FALSE;
|
|
mHasOpenScript=PR_FALSE;
|
|
mTokenRecycler=0;
|
|
mParserCommand=eViewNormal;
|
|
|
|
char* theEnvString = PR_GetEnv("ENABLE_STRICT");
|
|
mEnableStrict=PRBool(theEnvString!=0);
|
|
|
|
if(!gElementTable) {
|
|
gElementTable = new CElementTable();
|
|
}
|
|
|
|
#ifdef RICKG_DEBUG
|
|
//DebugDumpContainmentRules2(*this,"c:/temp/DTDRules.new","New COtherDTD Containment Rules");
|
|
nsHTMLElement::DebugDumpContainment("c:/temp/contain.new","ElementTable Rules");
|
|
nsHTMLElement::DebugDumpMembership("c:/temp/membership.out");
|
|
nsHTMLElement::DebugDumpContainType("c:/temp/ctnrules.out");
|
|
#endif
|
|
|
|
#ifdef NS_DEBUG
|
|
gNodeCount=0;
|
|
#endif
|
|
}
|
|
|
|
void COtherDTD::ReleaseTable(void) {
|
|
if(gElementTable) {
|
|
delete gElementTable;
|
|
gElementTable=0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method creates a new parser node. It tries to get one from
|
|
* the recycle list before allocating a new one.
|
|
* @update gess1/8/99
|
|
* @param
|
|
* @return valid node*
|
|
*/
|
|
nsCParserNode* COtherDTD::CreateNode(void) {
|
|
|
|
nsCParserNode* result=0;
|
|
if(0<mSharedNodes.GetSize()) {
|
|
result=(nsCParserNode*)mSharedNodes.Pop();
|
|
}
|
|
else{
|
|
result=new nsCParserNode();
|
|
#ifdef NS_DEBUG
|
|
#if 1
|
|
gNodeCount++;
|
|
#endif
|
|
#endif
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method recycles a given node
|
|
* @update gess1/8/99
|
|
* @param
|
|
* @return
|
|
*/
|
|
void COtherDTD::RecycleNode(nsCParserNode* aNode) {
|
|
if(aNode && (!aNode->mUseCount)) {
|
|
|
|
if(aNode->mToken) {
|
|
if(!aNode->mToken->mUseCount) {
|
|
mTokenRecycler->RecycleToken(aNode->mToken);
|
|
}
|
|
}
|
|
|
|
CToken* theToken=0;
|
|
while((theToken=(CToken*)aNode->PopAttributeToken())){
|
|
if(!theToken->mUseCount) {
|
|
mTokenRecycler->RecycleToken(theToken);
|
|
}
|
|
}
|
|
|
|
mSharedNodes.Push(aNode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method recycles the nodes on a nodestack.
|
|
* NOTE: Unlike recycleNode(), we force the usecount
|
|
* to 0 of all nodes, then force them to recycle.
|
|
* @update gess1/8/99
|
|
* @param aNodeStack
|
|
* @return nothing
|
|
*/
|
|
void COtherDTD::RecycleNodes(nsEntryStack *aNodeStack) {
|
|
if(aNodeStack) {
|
|
PRInt32 theCount=aNodeStack->mCount;
|
|
PRInt32 theIndex=0;
|
|
|
|
for(theIndex=0;theIndex<theCount;theIndex++) {
|
|
nsCParserNode* theNode=(nsCParserNode*)aNodeStack->NodeAt(theIndex);
|
|
if(theNode) {
|
|
|
|
theNode->mUseCount=0;
|
|
if(theNode->mToken) {
|
|
theNode->mToken->mUseCount=0;
|
|
mTokenRecycler->RecycleToken(theNode->mToken);
|
|
}
|
|
|
|
CToken* theToken=0;
|
|
while((theToken=(CToken*)theNode->PopAttributeToken())){
|
|
theNode->mToken->mUseCount=0;
|
|
mTokenRecycler->RecycleToken(theToken);
|
|
}
|
|
|
|
mSharedNodes.Push(theNode);
|
|
} //if
|
|
} //while
|
|
} //if
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @update gess1/8/99
|
|
* @param
|
|
* @return
|
|
*/
|
|
const nsIID& COtherDTD::GetMostDerivedIID(void)const {
|
|
return kClassIID;
|
|
}
|
|
|
|
/**
|
|
* Default destructor
|
|
*
|
|
* @update gess 4/9/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
COtherDTD::~COtherDTD(){
|
|
delete mBodyContext;
|
|
|
|
NS_IF_RELEASE(mTokenizer);
|
|
|
|
nsCParserNode* theNode=0;
|
|
|
|
#ifdef NS_DEBUG
|
|
#if 0
|
|
PRInt32 count=gNodeCount-mSharedNodes.GetSize();
|
|
if(count) {
|
|
printf("%i of %i nodes leaked!\n",count,gNodeCount);
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if 1
|
|
while((theNode=(nsCParserNode*)mSharedNodes.Pop())){
|
|
delete theNode;
|
|
}
|
|
#endif
|
|
|
|
#ifdef NS_DEBUG
|
|
gNodeCount=0;
|
|
#endif
|
|
|
|
NS_IF_RELEASE(mSink);
|
|
NS_IF_RELEASE(mDTDDebug);
|
|
}
|
|
|
|
/**
|
|
* 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_NewOtherHTMLDTD(nsIDTD** aInstancePtrResult) {
|
|
COtherDTD* it = new COtherDTD();
|
|
|
|
if (it == 0) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return it->QueryInterface(kClassIID, (void **) aInstancePtrResult);
|
|
}
|
|
|
|
/**
|
|
* Call this method if you want the DTD to construct a fresh
|
|
* instance of itself.
|
|
* @update gess7/23/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
nsresult COtherDTD::CreateNewInstance(nsIDTD** aInstancePtrResult){
|
|
nsresult result=NS_NewOtherHTMLDTD(aInstancePtrResult);
|
|
|
|
if(aInstancePtrResult) {
|
|
COtherDTD *theOtherDTD=(COtherDTD*)*aInstancePtrResult;
|
|
if(theOtherDTD) {
|
|
theOtherDTD->mDTDMode=mDTDMode;
|
|
theOtherDTD->mParserCommand=mParserCommand;
|
|
theOtherDTD->mDocType=mDocType;
|
|
theOtherDTD->mEnableStrict=mEnableStrict;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Called by the parser to initiate dtd verification of the
|
|
* internal context stack.
|
|
* @update gess 7/23/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
PRBool COtherDTD::Verify(nsString& aURLRef,nsIParser* aParser){
|
|
PRBool result=PR_TRUE;
|
|
|
|
/*
|
|
* Disable some DTD debugging code in the parser that
|
|
* breaks on some compilers because of some broken
|
|
* streams code in prstrm.cpp.
|
|
*/
|
|
#if !defined(MOZ_DISABLE_DTD_DEBUG)
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
if(mDTDDebug) {
|
|
// mDTDDebug->Verify(this,aParser,mBodyContext->GetCount(),mBodyContext->mStack,aURLRef);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
eAutoDetectResult COtherDTD::CanParse(CParserContext& aParserContext,nsString& aBuffer, PRInt32 aVersion) {
|
|
eAutoDetectResult result=eUnknownDetect;
|
|
|
|
if(mEnableStrict) {
|
|
if(eViewSource==aParserContext.mParserCommand) {
|
|
if(PR_TRUE==aParserContext.mMimeType.EqualsWithConversion(kPlainTextContentType)) {
|
|
result=eValidDetect;
|
|
}
|
|
else if(aParserContext.mMimeType.EqualsWithConversion(kRTFTextContentType)){
|
|
result=eValidDetect;
|
|
}
|
|
}
|
|
else {
|
|
if(PR_TRUE==aParserContext.mMimeType.EqualsWithConversion(kHTMLTextContentType)) {
|
|
result=(eDTDMode_strict==aParserContext.mDTDMode) ? ePrimaryDetect : eValidDetect;
|
|
}
|
|
else if(PR_TRUE==aParserContext.mMimeType.EqualsWithConversion(kPlainTextContentType)) {
|
|
result=eValidDetect;
|
|
}
|
|
else {
|
|
//otherwise, look into the buffer to see if you recognize anything...
|
|
PRBool theBufHasXML=PR_FALSE;
|
|
if(BufferContainsHTML(aBuffer,theBufHasXML)){
|
|
result = eValidDetect ;
|
|
if(0==aParserContext.mMimeType.Length()) {
|
|
aParserContext.SetMimeType(NS_ConvertToString(kHTMLTextContentType));
|
|
if(!theBufHasXML) {
|
|
result=(eDTDMode_strict==aParserContext.mDTDMode) ? ePrimaryDetect : eValidDetect;
|
|
}
|
|
else result=eValidDetect;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* The parser uses a code sandwich to wrap the parsing process. Before
|
|
* the process begins, WillBuildModel() is called. Afterwards the parser
|
|
* calls DidBuildModel().
|
|
* @update rickg 03.20.2000
|
|
* @param aParserContext
|
|
* @param aSink
|
|
* @return error code (almost always 0)
|
|
*/
|
|
nsresult COtherDTD::WillBuildModel( const CParserContext& aParserContext,nsIContentSink* aSink){
|
|
nsresult result=NS_OK;
|
|
|
|
mFilename=aParserContext.mScanner->GetFilename();
|
|
mHasOpenBody=PR_FALSE;
|
|
mHadFrameset=PR_FALSE;
|
|
mLineNumber=1;
|
|
mHasOpenScript=PR_FALSE;
|
|
mDTDMode=aParserContext.mDTDMode;
|
|
mParserCommand=aParserContext.mParserCommand;
|
|
|
|
if((!aParserContext.mPrevContext) && (aSink)) {
|
|
|
|
STOP_TIMER();
|
|
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: COtherDTD::WillBuildModel(), this=%p\n", this));
|
|
|
|
mTokenRecycler=0;
|
|
|
|
mDocType=aParserContext.mDocType;
|
|
if(aSink && (!mSink)) {
|
|
result=aSink->QueryInterface(kIHTMLContentSinkIID, (void **)&mSink);
|
|
}
|
|
|
|
if(result==NS_OK) {
|
|
result = aSink->WillBuildModel();
|
|
|
|
mBodyContext->ResetCounters();
|
|
|
|
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: COtherDTD::WillBuildModel(), this=%p\n", this));
|
|
START_TIMER();
|
|
|
|
mSkipTarget=eHTMLTag_unknown;
|
|
mComputedCRC32=0;
|
|
mExpectedCRC32=0;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This is called when it's time to read as many tokens from the tokenizer
|
|
* as you can. Not all tokens may make sense, so you may not be able to
|
|
* read them all (until more come in later).
|
|
*
|
|
* @update gess5/18/98
|
|
* @param aParser is the parser object that's driving this process
|
|
* @return error code (almost always NS_OK)
|
|
*/
|
|
nsresult COtherDTD::BuildModel(nsIParser* aParser,nsITokenizer* aTokenizer,nsITokenObserver* anObserver,nsIContentSink* aSink) {
|
|
nsresult result=NS_OK;
|
|
|
|
if(aTokenizer) {
|
|
nsITokenizer* oldTokenizer=mTokenizer;
|
|
mTokenizer=aTokenizer;
|
|
mParser=(nsParser*)aParser;
|
|
|
|
if(mTokenizer) {
|
|
|
|
mTokenRecycler=(CTokenRecycler*)mTokenizer->GetTokenRecycler();
|
|
if(mSink) {
|
|
|
|
if(!mBodyContext->GetCount()) {
|
|
//if the content model is empty, then begin by opening <html>...
|
|
CStartToken *theToken=(CStartToken*)mTokenRecycler->CreateTokenOfType(eToken_start,eHTMLTag_html,NS_ConvertToString("html"));
|
|
HandleStartToken(theToken); //this token should get pushed on the context stack, don't recycle it.
|
|
}
|
|
|
|
while(NS_SUCCEEDED(result)){
|
|
|
|
if(mDTDState!=NS_ERROR_HTMLPARSER_STOPPARSING) {
|
|
CToken* theToken=mTokenizer->PopToken();
|
|
if(theToken) {
|
|
result=HandleToken(theToken,aParser);
|
|
}
|
|
else break;
|
|
}
|
|
else {
|
|
result=mDTDState;
|
|
break;
|
|
}
|
|
}//while
|
|
mTokenizer=oldTokenizer;
|
|
}
|
|
}
|
|
}
|
|
else result=NS_ERROR_HTMLPARSER_BADTOKENIZER;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @update gess5/18/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
nsresult COtherDTD::DidBuildModel(nsresult anErrorCode,PRBool aNotifySink,nsIParser* aParser,nsIContentSink* aSink){
|
|
nsresult result=NS_OK;
|
|
|
|
if(aSink) {
|
|
|
|
if(aParser && (NS_OK==result)){
|
|
if(aNotifySink){
|
|
if((NS_OK==anErrorCode) && (mBodyContext->GetCount()>0)) {
|
|
|
|
PRInt32 theIndex=mBodyContext->GetCount()-1;
|
|
eHTMLTags theChild = mBodyContext->TagAt(theIndex);
|
|
while(theIndex>0) {
|
|
eHTMLTags theParent= mBodyContext->TagAt(--theIndex);
|
|
CElement *theElement=gElementTable->mElements[theParent];
|
|
nsIParserNode *theNode=mBodyContext->PeekNode();
|
|
theElement->HandleEndToken(theNode,theChild,mBodyContext,mSink);
|
|
theChild=theParent;
|
|
}
|
|
|
|
nsEntryStack *theChildStyles=0;
|
|
nsCParserNode* theNode=(nsCParserNode*)mBodyContext->Pop(theChildStyles);
|
|
if(theNode) {
|
|
mSink->CloseHTML(*theNode);
|
|
}
|
|
|
|
}
|
|
else {
|
|
//If you're here, then an error occured, but we still have nodes on the stack.
|
|
//At a minimum, we should grab the nodes and recycle them.
|
|
//Just to be correct, we'll also recycle the nodes.
|
|
|
|
while(mBodyContext->GetCount() > 0) {
|
|
|
|
nsEntryStack *theChildStyles=0;
|
|
nsCParserNode* theNode=(nsCParserNode*)mBodyContext->Pop(theChildStyles);
|
|
theNode->mUseCount=0;
|
|
RecycleNode(theNode);
|
|
if(theChildStyles) {
|
|
delete theChildStyles;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
} //if aparser
|
|
|
|
//No matter what, you need to call did build model.
|
|
result=aSink->DidBuildModel(0);
|
|
|
|
} //if asink
|
|
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 12/1/99
|
|
* @param aToken
|
|
* @param aParser
|
|
* @return
|
|
*/
|
|
nsresult COtherDTD::HandleToken(CToken* aToken,nsIParser* aParser){
|
|
nsresult result=NS_OK;
|
|
|
|
if(aToken) {
|
|
CHTMLToken* theToken= (CHTMLToken*)(aToken);
|
|
eHTMLTokenTypes theType=eHTMLTokenTypes(theToken->GetTokenType());
|
|
// eHTMLTags theTag=(eHTMLTags)theToken->GetTypeID();
|
|
// PRBool execSkipContent=PR_FALSE;
|
|
|
|
theToken->mUseCount=0; //assume every token coming into this system needs recycling.
|
|
|
|
|
|
mParser=(nsParser*)aParser;
|
|
|
|
switch(theType) {
|
|
case eToken_text:
|
|
case eToken_start:
|
|
case eToken_whitespace:
|
|
case eToken_newline:
|
|
case eToken_doctypeDecl:
|
|
result=HandleStartToken(theToken); break;
|
|
|
|
case eToken_end:
|
|
result=HandleEndToken(theToken); break;
|
|
|
|
default:
|
|
break;
|
|
}//switch
|
|
|
|
|
|
if(NS_SUCCEEDED(result) || (NS_ERROR_HTMLPARSER_BLOCK==result)) {
|
|
if(0>=theToken->mUseCount)
|
|
mTokenRecycler->RecycleToken(theToken);
|
|
}
|
|
else if(result==NS_ERROR_HTMLPARSER_STOPPARSING)
|
|
mDTDState=result;
|
|
else return NS_OK;
|
|
|
|
}//if
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* This gets called after we've handled a given start tag.
|
|
* It's a generic hook to let us to post processing.
|
|
* @param aToken contains the tag in question
|
|
* @param aChildTag is the tag itself.
|
|
* @return status
|
|
*/
|
|
nsresult COtherDTD::DidHandleStartTag(nsCParserNode& aNode,eHTMLTags aChildTag){
|
|
nsresult result=NS_OK;
|
|
|
|
switch(aChildTag){
|
|
|
|
case eHTMLTag_script:
|
|
mHasOpenScript=PR_TRUE;
|
|
break;
|
|
|
|
case eHTMLTag_pre:
|
|
case eHTMLTag_listing:
|
|
{
|
|
CToken* theNextToken=mTokenizer->PeekToken();
|
|
if(theNextToken) {
|
|
eHTMLTokenTypes theType=eHTMLTokenTypes(theNextToken->GetTokenType());
|
|
if(eToken_newline==theType){
|
|
mLineNumber++;
|
|
mTokenizer->PopToken(); //skip 1st newline inside PRE and LISTING
|
|
}//if
|
|
}//if
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}//switch
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#ifdef RICKG_DEBUG
|
|
void WriteTokenToLog(CToken* aToken) {
|
|
|
|
static nsFileSpec fileSpec("c:\\temp\\tokenlog.html");
|
|
static nsOutputFileStream outputStream(fileSpec);
|
|
aToken->DebugDumpSource(outputStream); //write token without close bracket...
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* This gets called before we've handled a given start tag.
|
|
* It's a generic hook to let us do pre processing.
|
|
* @param aToken contains the tag in question
|
|
* @param aChildTag is the tag itself.
|
|
* @param aNode is the node (tag) with associated attributes.
|
|
* @return TRUE if tag processing should continue; FALSE if the tag has been handled.
|
|
*/
|
|
nsresult COtherDTD::WillHandleStartTag(CToken* aToken,eHTMLTags aTag,nsCParserNode& aNode){
|
|
nsresult result=NS_OK;
|
|
|
|
//first let's see if there's some skipped content to deal with...
|
|
#if 0
|
|
PRInt32 theAttrCount = aNode.GetAttributeCount();
|
|
if(*gElementTable->mElements[aTag].mSkipTarget) {
|
|
result=CollectSkippedContent(aNode,theAttrCount);
|
|
}
|
|
#endif
|
|
|
|
STOP_TIMER()
|
|
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: COtherDTD::WillHandleStartTag(), this=%p\n", this));
|
|
|
|
if(mParser) {
|
|
|
|
switch(aTag) {
|
|
case eHTMLTag_newline:
|
|
mLineNumber++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
CObserverService* theService=mParser->GetObserverService();
|
|
if(theService) {
|
|
CParserContext* pc=mParser->PeekContext();
|
|
void* theDocID=(pc)? pc->mKey:0;
|
|
|
|
result=theService->Notify(aTag,aNode,theDocID, NS_ConvertToString(kHTMLTextContentType), mParser);
|
|
}
|
|
}
|
|
|
|
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: COtherDTD::WillHandleStartTag(), this=%p\n", this));
|
|
START_TIMER()
|
|
|
|
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 1/04/99
|
|
* @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 COtherDTD::HandleStartToken(CToken* aToken) {
|
|
NS_PRECONDITION(0!=aToken,kNullToken);
|
|
|
|
#ifdef RICKG_DEBUG
|
|
WriteTokenToLog(aToken);
|
|
#endif
|
|
|
|
//Begin by gathering up attributes...
|
|
|
|
nsCParserNode* theNode=CreateNode();
|
|
theNode->Init(aToken,mLineNumber,mTokenRecycler);
|
|
|
|
eHTMLTags theChildTag=(eHTMLTags)aToken->GetTypeID();
|
|
PRInt16 attrCount=aToken->GetAttributeCount();
|
|
eHTMLTags theParent=mBodyContext->Last();
|
|
nsresult result=(0==attrCount) ? NS_OK : CollectAttributes(*theNode,theChildTag,attrCount);
|
|
|
|
if(NS_OK==result) {
|
|
result=WillHandleStartTag(aToken,theChildTag,*theNode);
|
|
if(NS_OK==result) {
|
|
|
|
mLineNumber += aToken->mNewlineCount;
|
|
|
|
PRBool theTagWasHandled=PR_FALSE;
|
|
|
|
switch(theChildTag) {
|
|
|
|
case eHTMLTag_html:
|
|
if(!HasOpenContainer(theChildTag)) {
|
|
mSink->OpenHTML(*theNode);
|
|
mBodyContext->Push(theNode,0);
|
|
}
|
|
theTagWasHandled=PR_TRUE;
|
|
break;
|
|
|
|
default:
|
|
CElement* theElement=gElementTable->mElements[theParent];
|
|
if(theElement) {
|
|
result=theElement->HandleStartToken(theNode,theChildTag,mBodyContext,mSink);
|
|
theTagWasHandled=PR_TRUE;
|
|
}
|
|
break;
|
|
}//switch
|
|
|
|
if(theTagWasHandled) {
|
|
DidHandleStartTag(*theNode,theChildTag);
|
|
}
|
|
|
|
} //if
|
|
}//if
|
|
|
|
RecycleNode(theNode);
|
|
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 COtherDTD::HandleEndToken(CToken* aToken) {
|
|
NS_PRECONDITION(0!=aToken,kNullToken);
|
|
|
|
nsresult result=NS_OK;
|
|
eHTMLTags theChildTag=(eHTMLTags)aToken->GetTypeID();
|
|
|
|
#ifdef RICKG_DEBUG
|
|
WriteTokenToLog(aToken);
|
|
#endif
|
|
|
|
switch(theChildTag) {
|
|
|
|
case eHTMLTag_body: //we intentionally don't let the user close HTML or BODY
|
|
case eHTMLTag_html:
|
|
break;
|
|
|
|
case eHTMLTag_script:
|
|
mHasOpenScript=PR_FALSE;
|
|
|
|
default:
|
|
PRInt32 theCount=mBodyContext->GetCount();
|
|
eHTMLTags theParent=mBodyContext->TagAt(theCount-1);
|
|
if(theChildTag==theParent) {
|
|
theParent=mBodyContext->TagAt(theCount-2);
|
|
}
|
|
CElement* theElement=gElementTable->mElements[theParent];
|
|
if(theElement) {
|
|
nsCParserNode theNode((CHTMLToken*)aToken,mLineNumber);
|
|
result=theElement->HandleEndToken(&theNode,theChildTag,mBodyContext,mSink);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
*/
|
|
nsresult COtherDTD::CollectAttributes(nsCParserNode& aNode,eHTMLTags aTag,PRInt32 aCount){
|
|
int attr=0;
|
|
|
|
nsresult result=NS_OK;
|
|
int theAvailTokenCount=mTokenizer->GetCount();
|
|
if(aCount<=theAvailTokenCount) {
|
|
CToken* theToken=0;
|
|
eHTMLTags theSkipTarget=gElementTable->mElements[aTag]->GetSkipTarget();
|
|
for(attr=0;attr<aCount;attr++){
|
|
theToken=mTokenizer->PopToken();
|
|
if(theToken) {
|
|
// Sanitize the key for it might contain some non-alpha-non-digit characters
|
|
// at its end. Ex. <OPTION SELECTED/> - This will be tokenized as "<" "OPTION",
|
|
// "SELECTED/", and ">". In this case the "SELECTED/" key will be sanitized to
|
|
// a legitimate "SELECTED" key.
|
|
((CAttributeToken*)theToken)->SanitizeKey();
|
|
|
|
#ifdef RICKG_DEBUG
|
|
WriteTokenToLog(theToken);
|
|
#endif
|
|
|
|
aNode.AddAttribute(theToken);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
result=kEOF;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/***********************************************************************************
|
|
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 COtherDTD::CanContain(PRInt32 aParent,PRInt32 aChild) const {
|
|
PRBool result=PR_FALSE;
|
|
//result=gHTMLElements[aParent].CanContain((eHTMLTags)aChild);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Give rest of world access to our tag enums, so that CanContain(), etc,
|
|
* become useful.
|
|
*/
|
|
NS_IMETHODIMP COtherDTD::StringTagToIntTag(nsString &aTag, PRInt32* aIntTag) const {
|
|
*aIntTag = nsHTMLTags::LookupTag(aTag);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP COtherDTD::IntTagToStringTag(PRInt32 aIntTag, nsString& aTag) const {
|
|
aTag.AssignWithConversion(nsHTMLTags::GetStringValue((nsHTMLTag)aIntTag));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP COtherDTD::ConvertEntityToUnicode(const nsString& aEntity, PRInt32* aUnicode) const {
|
|
*aUnicode = nsHTMLEntities::EntityToUnicode(aEntity);
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* This method gets called to determine whether a given
|
|
* tag is itself a container
|
|
*
|
|
* @update gess 4/8/98
|
|
* @param aTag -- tag to test as a container
|
|
* @return PR_TRUE if given tag can contain other tags
|
|
*/
|
|
PRBool COtherDTD::IsContainer(PRInt32 aTag) const {
|
|
return gElementTable->mElements[eHTMLTags(aTag)]->IsContainer();
|
|
}
|
|
|
|
/**
|
|
* This method allows the caller to determine if a given container
|
|
* is currently open
|
|
*
|
|
* @update gess 11/9/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
PRBool COtherDTD::HasOpenContainer(eHTMLTags aContainer) const {
|
|
PRBool result=PR_FALSE;
|
|
|
|
switch(aContainer) {
|
|
case eHTMLTag_form:
|
|
result=mHasOpenForm; break;
|
|
case eHTMLTag_map:
|
|
result=mHasOpenMap; break;
|
|
default:
|
|
result=mBodyContext->HasOpenContainer(aContainer);
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* This method allows the caller to determine if a any member
|
|
* in a set of tags is currently open
|
|
*
|
|
* @update gess 11/9/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
PRBool COtherDTD::HasOpenContainer(const eHTMLTags aTagSet[],PRInt32 aCount) const {
|
|
|
|
int theIndex;
|
|
int theTopIndex=mBodyContext->GetCount()-1;
|
|
|
|
for(theIndex=theTopIndex;theIndex>0;theIndex--){
|
|
if(FindTagInSet((*mBodyContext)[theIndex],aTagSet,aCount))
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
* @param aTag -- id of tag to be closed
|
|
* @param aClosedByStartTag -- ONLY TRUE if the container is being closed by opening of another container.
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult
|
|
COtherDTD::CloseContainer(const nsIParserNode *aNode,eHTMLTags aTarget,PRBool aClosedByStartTag){
|
|
nsresult result=NS_OK;
|
|
eHTMLTags nodeType=(eHTMLTags)aNode->GetNodeType();
|
|
|
|
#ifdef ENABLE_CRC
|
|
#define K_CLOSEOP 200
|
|
CRCStruct theStruct(nodeType,K_CLOSEOP);
|
|
mComputedCRC32=AccumulateCRC(mComputedCRC32,(char*)&theStruct,sizeof(theStruct));
|
|
#endif
|
|
|
|
switch(nodeType) {
|
|
|
|
case eHTMLTag_html:
|
|
// result=CloseHTML(aNode); break;
|
|
|
|
case eHTMLTag_map:
|
|
// result=CloseMap(aNode);
|
|
break;
|
|
|
|
case eHTMLTag_form:
|
|
// result=CloseForm(aNode);
|
|
break;
|
|
|
|
case eHTMLTag_frameset:
|
|
// result=CloseFrameset(aNode);
|
|
break;
|
|
|
|
case eHTMLTag_title:
|
|
default:
|
|
|
|
STOP_TIMER();
|
|
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: COtherDTD::CloseContainer(), this=%p\n", this));
|
|
|
|
result=(mSink) ? mSink->CloseContainer(*aNode) : NS_OK;
|
|
|
|
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: COtherDTD::CloseContainer(), this=%p\n", this));
|
|
START_TIMER();
|
|
|
|
break;
|
|
}
|
|
|
|
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 anIndex
|
|
* @param aTag
|
|
* @param aClosedByStartTag -- if TRUE, then we're closing something because a start tag caused it
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult COtherDTD::CloseContainersTo(PRInt32 anIndex,eHTMLTags aTarget, PRBool aClosedByStartTag){
|
|
NS_PRECONDITION(mBodyContext->GetCount() > 0, kInvalidTagStackPos);
|
|
nsresult result=NS_OK;
|
|
|
|
|
|
if((anIndex<mBodyContext->GetCount()) && (anIndex>=0)) {
|
|
|
|
while(mBodyContext->GetCount()>anIndex) {
|
|
|
|
// eHTMLTags theTag=mBodyContext->Last();
|
|
nsEntryStack *theChildStyleStack=0;
|
|
nsCParserNode *theNode=(nsCParserNode*)mBodyContext->Pop(theChildStyleStack);
|
|
|
|
if(theNode) {
|
|
result=CloseContainer(theNode,aTarget,aClosedByStartTag);
|
|
|
|
}//if anode
|
|
RecycleNode(theNode);
|
|
}
|
|
|
|
} //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/6/98
|
|
* @param aTag --
|
|
* @param aClosedByStartTag -- ONLY TRUE if the container is being closed by opening of another container.
|
|
* @return TRUE if ok, FALSE if error
|
|
*/
|
|
nsresult COtherDTD::CloseContainersTo(eHTMLTags aTarget,PRBool aClosedByStartTag){
|
|
NS_PRECONDITION(mBodyContext->GetCount() > 0, kInvalidTagStackPos);
|
|
|
|
nsresult result=NS_OK;
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieve the preferred tokenizer for use by this DTD.
|
|
* @update gess12/28/98
|
|
* @param none
|
|
* @return ptr to tokenizer
|
|
*/
|
|
nsresult COtherDTD::GetTokenizer(nsITokenizer*& aTokenizer) {
|
|
nsresult result=NS_OK;
|
|
if(!mTokenizer) {
|
|
result=NS_NewHTMLTokenizer(&mTokenizer,mDTDMode,mDocType,mParserCommand);
|
|
}
|
|
aTokenizer=mTokenizer;
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* @update gess8/4/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
nsITokenRecycler* COtherDTD::GetTokenRecycler(void){
|
|
if(!mTokenRecycler) {
|
|
nsresult result=GetTokenizer(mTokenizer);
|
|
if (NS_SUCCEEDED(result)) {
|
|
mTokenRecycler=(CTokenRecycler*)mTokenizer->GetTokenRecycler();
|
|
}
|
|
}
|
|
return mTokenRecycler;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @update gess5/18/98
|
|
* @param
|
|
* @return
|
|
*/
|
|
nsresult COtherDTD::WillResumeParse(void) {
|
|
|
|
STOP_TIMER();
|
|
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: COtherDTD::WillResumeParse(), this=%p\n", this));
|
|
|
|
nsresult result=(mSink) ? mSink->WillResume() : NS_OK;
|
|
|
|
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: COtherDTD::WillResumeParse(), this=%p\n", this));
|
|
START_TIMER();
|
|
|
|
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 COtherDTD::WillInterruptParse(void){
|
|
|
|
STOP_TIMER();
|
|
MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: COtherDTD::WillInterruptParse(), this=%p\n", this));
|
|
|
|
nsresult result=(mSink) ? mSink->WillInterrupt() : NS_OK;
|
|
|
|
MOZ_TIMER_DEBUGLOG(("Start: Parse Time: COtherDTD::WillInterruptParse(), this=%p\n", this));
|
|
START_TIMER();
|
|
|
|
return result;
|
|
}
|
|
|