Mozilla FireFox Gecko内核源代码解析(2.nsTokenizer)

Mozilla FireFox Gecko内核源代码解析

(1.nsTokenizer)

中科院计算技术研究所网络数据科学与工程研究中心

信息抽取小组

耿耘

[email protected]

前面我们大体介绍了nsParser的主控流程(nsParser.cpp),可知HTML解析一般分为两个阶段,即文法阶段的分词操作,和语法阶段的解析操作,前者一般来讲就是将HTML的标签分开,分成一个个的Token,而在Mozilla Firefox中,这个工作的主要流程是由nsHTMLTokenizer(分词器)控制下的nsHTMLToken来完成的。nsHTMLTokenizer负责响应nsParser传输过来的分析请求,并调用相应的nsHTMLToken,具体的词法,属性分析等都是放在后者nsHTMLTokens中完成的。其中还要用到对字符串进行流式扫描和读取进行支持的nsScanner。

值得注意的是nsHTMLTokenizer继承自nsITokenizer接口,实际上Mozilla还针对将来可能出现的其他XML格式文档进行了接口的制定,也许是说如过HTML5,6,7出来后,我们依然可以复用一部分接口来制定新的Tokenizer。

而目前我们主要使用的就是HTMLTokenizer,它主要针对各种HTML的标签进行解析,它将HTML的标签划分为了13类(实际上没这么多),这在一个叫做eHTMLTokenTypes的枚举类型中进行了定义,我们在之后的nsHTMLToken分析中会详细进行解析,这里我们为了助于理解Tokenizer的工作原理,先来看一下这个类型的集合:

eHTMLTokenTypes

enumeHTMLTokenTypes {

eToken_unknown=0,

eToken_start=1,eToken_end,eToken_comment,eToken_entity,

eToken_whitespace,eToken_newline,eToken_text,eToken_attribute,

eToken_instruction,eToken_cdatasection, eToken_doctypeDecl, eToken_markupDecl,

eToken_last //make sure this stays the lasttoken...

};

可以看到,其中eToken_last,eToken_unknow等是为了进行一些其他处理而存在的。其他的就是我们Mozilla中队常用的HTML标签的类型进行的分类。

观察头文件可以看出,它主要的方法是以ConsumeXXX()的样式来命名的方法,并可以看出,所有方法的参数中都要添加nsScanner类型的参数,这其实潜在地表示,Tokenizer和Scanner不必一对一配套使用。

它还提供了一个很重要的ScanDocStructure()方法,通过一个栈来对文档中所有Tokens的良构性进行一个判断,即基本的文法正确性检查。

首先,我们来看它的头文件:

nsTokenizer.h

#ifndef__NSHTMLTOKENIZER

#define__NSHTMLTOKENIZER

#include "nsISupports.h"

#include "nsITokenizer.h"

#include "nsIDTD.h"

#include "prtypes.h"

#include "nsDeque.h"

#include "nsScanner.h"

#include "nsHTMLTokens.h"

#include "nsDTDUtils.h"

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

Notes:

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

#ifdef_MSC_VER

#pragma warning( disable :4275 )

#endif

classnsHTMLTokenizer : public nsITokenizer {

public:

NS_DECL_ISUPPORTS //之前构件代码中#Define的方法

NS_DECL_NSITOKENIZER

nsHTMLTokenizer(nsDTDMode aParseMode = eDTDMode_quirks, //构造方法

eParserDocType aDocType =eHTML_Quirks,

eParserCommands aCommand =eViewNormal,

PRUint32 aFlags = 0);

virtual ~nsHTMLTokenizer(); //析构方法

static PRUint32 GetFlags(constnsIContentSink* aSink); //获取本Tokenizer的标示位

protected:

//下面的方法都是针对不同的HTMl词条类型来进行解析处理的

nsresult ConsumeTag(PRUnichar aChar,CToken*& aToken,nsScanner&aScanner,PRBool& aFlushTokens);

nsresult ConsumeStartTag(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner,PRBool& aFlushTokens);

nsresult ConsumeEndTag(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeAttributes(PRUnichar aChar, CToken* aToken,nsScanner& aScanner);

nsresult ConsumeEntity(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeWhitespace(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeComment(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeNewline(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeText(CToken*& aToken,nsScanner& aScanner);

nsresult ConsumeSpecialMarkup(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

nsresult ConsumeProcessingInstruction(PRUnichar aChar,CToken*&aToken,nsScanner& aScanner);

//这个方法是对当前词条队列中所有词条进行良构分析的方法

nsresult ScanDocStructure(PRBool aIsFinalChunk);

//添加新的Token到队列中去

static void AddToken(CToken*& aToken,nsresultaResult,nsDeque* aDeque,nsTokenAllocator* aTokenAllocator);

nsDeque mTokenDeque; //存放Token的队列

PRPackedBool mIsFinalChunk; //标注是否是最后一个数据块

nsTokenAllocator*mTokenAllocator; //这个是用来分配Token的Allocator,在Mozilla中,为了节省内存资源,对于Token我们都是通过TokenAllocator进行分配的,这个我们在相应代码的解析之中会进行分析的

// This variable saves the position of the last tag weinspected in

// ScanDocStructure. We start scanning the generalwell-formedness of the

// document starting at this position each time.

//下面这个变量记录了我们在ScanDocStructure中所处理到的最后一个tag的位置。我们每次对文档进行良构性扫描的时候都会从这个位置开始。

PRInt32 mTokenScanPos;

//下面这个变量是用来记录分词器状态的标示位

PRUint32 mFlags;

};

#endif

以上就是nsHTMLTokenizer的头文件,下面我们就来看其cpp文件的真正实现部分。

这主要是对nsITokenizer接口的实现。这个文件包含了一个对HTML文档进行分词的分词器的实现。它尝试在对老版本的解析器的兼容性和对SGML标准的支持之上进行这些工作。注意到,大部分真正的“分词”过程是在nsHTMLTokens.cpp中进行的。

nsTokenizer.cpp

#include "nsIAtom.h"

#include "nsHTMLTokenizer.h"

#include "nsScanner.h"

#include "nsElementTable.h"

#include "nsReadableUtils.h"

#include "nsUnicharUtils.h"

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

And now for the main class --nsHTMLTokenizer...

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

/**

* Satisfy the nsISupports interface.

*/

//下面这个主要是为了实现nsISupports接口,具体实现在之前的#Define中实现了

NS_IMPL_ISUPPORTS1(nsHTMLTokenizer,nsITokenizer)

//下面是nsHTMLTokenizer的默认构造方法:

/**

* Default constructor

*

* @paramaParseMode The current mode the document is in (quirks, etc.)

* @param aDocType Thedocument type of the current document

* @param aCommand Whatwe are trying to do (view-source, parse a fragment, etc.)

*/

nsHTMLTokenizer::nsHTMLTokenizer(nsDTDModeaParseMode,

eParserDocTypeaDocType,

eParserCommands aCommand,

PRUint32aFlags)

: mTokenDeque(0), mFlags(aFlags)

//构造方法,初始化两个变量,清空Token存放队列,并用aFlags设置Tokenizer的状态位

{

//首先,要根据aParseMode来设置mFlags

if (aParseMode == eDTDMode_full_standards ||

aParseMode == eDTDMode_almost_standards) {

mFlags |= NS_IPARSER_FLAG_STRICT_MODE;

} else if(aParseMode == eDTDMode_quirks) {

mFlags |= NS_IPARSER_FLAG_QUIRKS_MODE;

} else if(aParseMode == eDTDMode_autodetect) {

mFlags |= NS_IPARSER_FLAG_AUTO_DETECT_MODE;

} else {

mFlags |= NS_IPARSER_FLAG_UNKNOWN_MODE;

}

//之后还要根据aDocType来对mFlags进行设置

if (aDocType == ePlainText) {

mFlags |= NS_IPARSER_FLAG_PLAIN_TEXT;

} else if(aDocType == eXML) {

mFlags |= NS_IPARSER_FLAG_XML;

} else if(aDocType == eHTML_Quirks ||

aDocType == eHTML_Strict) {

mFlags |= NS_IPARSER_FLAG_HTML;

}

//根据aCommand来设置mFlag标示位是VIEW_SOURCE或VIEW_NORMAL

mFlags |= aCommand == eViewSource

? NS_IPARSER_FLAG_VIEW_SOURCE

: NS_IPARSER_FLAG_VIEW_NORMAL;

//判断,不能为XML模式,而且必须为VIEW_SOURCE模式?

NS_ASSERTION(!(mFlags & NS_IPARSER_FLAG_XML) ||

(mFlags &NS_IPARSER_FLAG_VIEW_SOURCE),

"Whyisn't this XML document going through our XML parser?");

//初始化,清空另两个数据成员变量

mTokenAllocator = nsnull;

mTokenScanPos = 0;

}

//下面是nsHTMLTokenizer默认的析构方法,注意到里面需要用到一个叫做ArenaPool的内存分配机制,这个机制是Mozilla中推出的一种内存分配机制,具体的方法我们在其他的代码解析文档中会说,有兴趣的读者也可以自己去看一下。就是为了尽可能减少内存碎片而设计的一种机制,FireFox的JSEngine即SpiderMonkey中也用到了这个机制。

/**

* The destructor ensures that we don't leakany left over tokens.

*/

nsHTMLTokenizer::~nsHTMLTokenizer()

{

if (mTokenDeque.GetSize()) { //如果当前的Token队列存在

CTokenDeallocator theDeallocator(mTokenAllocator->GetArenaPool()); //获取对应的Deallocator

mTokenDeque.ForEach(theDeallocator); //对每个mTokenDeque里的Token运行theDeallocator

}

}

//获取nsHTMLTokenizer的mFlag标示位。

/*static*/PRUint32

nsHTMLTokenizer::GetFlags(const nsIContentSink* aSink)

{

PRUint32 flags = 0;

nsCOMPtr sink = //这种构建方法需要了解

do_QueryInterface(const_cast(aSink));

if (sink) { //如果获取Sink成功

PRBool enabled = PR_TRUE; //申请一个BOOL变量enabled,默认为为TRUE

sink->IsEnabled(eHTMLTag_frameset, &enabled); //获取sink是否启用了Tag_frameset的标示

if (enabled) { //如果启用了

flags |= NS_IPARSER_FLAG_FRAMES_ENABLED; //设置相应的标示位

}

sink->IsEnabled(eHTMLTag_script, &enabled); //获取sink是否启用了Tag_sript的标示

if (enabled) { //如果启用了

flags |= NS_IPARSER_FLAG_SCRIPT_ENABLED; //设置相应的标示位

}

}

return flags;

}

//上面一些方法都是对分词过程进行支持的,下面我们来看看真正的分词方法。

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

Here begins the real working methods for thetokenizer.

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

/**

* Adds a token onto the end of the deque ifaResult is a successful result.

* Otherwise, this function frees aToken andsets it to nsnull.

*

* @param aToken The token that wants to beadded.

* @param aResult The error code that will beused to determine if we actually

* want to push this token.

* @param aDeque The deque we want to pushaToken onto.

* @param aTokenAllocator The allocator we useto free aToken in case aResult

* is not a success code.

*/

/* static */

//AddToken顾名思义,就是添加一个新的Token到存放Tokens的队列尾部。其他情况下,即如果不成功的话(aResult不为TRUE),则我们会释放aToken并将其设置为nsnull。

void

nsHTMLTokenizer::AddToken(CToken*& aToken,

nsresult aResult,

nsDeque* aDeque,

nsTokenAllocator*aTokenAllocator)

{

if (aToken && aDeque) {

if (NS_SUCCEEDED(aResult)) { //首先判断aResult是否成功

aDeque->Push(aToken); //将aToken推入队列

} else { //其他情况下,即aResult不成功

IF_FREE(aToken, aTokenAllocator); //释放aToken

}

}

}

//以上方法和接下来的几个方法需要注意到的是,aToken是存放在一中叫做nsDeque的队列型数据结构中的,因此其会提供相应的push(),peek()方法等,具体的可以去看具体的数据结构,我们这里只需要调用该数据结构提供的方法即可。

/**

* Retrieve a pointer to the global tokenrecycler...

*

* @return Pointer to recycler (or null)

*/

nsTokenAllocator* //获取全局的token回收器

nsHTMLTokenizer::GetTokenAllocator()

{

return mTokenAllocator; //返回mTokenAllocator

}

//查看队列头部Token的PeekToken方法

/**

* This method provides access to the topmosttoken in the tokenDeque.

* The token is not really removed from thelist.

*

* @return Pointer to token

*/

CToken*

nsHTMLTokenizer::PeekToken()

{

return (CToken*)mTokenDeque.PeekFront(); //查看队列头部的Token,该Token不会出队

}

//获取队列头部Token,并将其出队的PopToken方法

/**

* This method provides access to the topmosttoken in the tokenDeque.

* The token is really removed from the list;if the list is empty we return 0.

*

* @return Pointer to token or NULL

*/

CToken*

nsHTMLTokenizer::PopToken()

{

return (CToken*)mTokenDeque.PopFront(); //直接获取头部Token,如果是空的队列,则会返回0

}

//将Token压入到队列的头部,并且返回这个Token(我个人感觉应当返回压入操作的成功与否)

/**

* Pushes a token onto the front of our dequesuch that the next call to

* PopToken() or PeekToken() will return thattoken.

*

* @param theToken The next token to beprocessed

* @return theToken

*/

CToken*

nsHTMLTokenizer::PushTokenFront(CToken*theToken)

{

mTokenDeque.PushFront(theToken); //压入操作

return theToken; //返回该Token

}

//将Token压入队列的尾部,并返回相应的Token(操作结果就不判断了么?)

/**

* Pushes a token onto the front of our dequesuch that the next call to

* PopToken() or PeekToken() will return thattoken.

*

* @param theToken The next token to beprocessed

* @return theToken

*/

CToken*

nsHTMLTokenizer::PushTokenFront(CToken*theToken)

{

mTokenDeque.PushFront(theToken); //压入操作

return theToken; //返回该Token

}

//返回队列的大小

/**

* Returns the size of the deque.

*

* @return The number of remaining tokens.

*/

PRInt32

nsHTMLTokenizer::GetCount()

{

return mTokenDeque.GetSize(); //获取该deque的大小

}

//获取队列中相应位置的Token

/**

* Allows access to an arbitrary token in thedeque. The accessed token is left

* in the deque.

*

* @param anIndex The index of the targettoken. Token 0 would be the same as

* the result of a call toPeekToken()

* @return The requested token.

*/

CToken*

nsHTMLTokenizer::GetTokenAt(PRInt32anIndex)

{

return (CToken*)mTokenDeque.ObjectAt(anIndex); //类似数组,获取下标为anIndex的元素,注意这里的ObjectAt方法,是构件方法

}

//下面,我们来看看更分词操作有关的一系列动作操作:

首先来看很经典的三部曲操作中用来初始化的Will系列操作(对应的还有本体操作和收尾用的Did系列操作)

/**

* This method is part of the"sandwich" that occurs when we want to tokenize

* a document. This prepares us to be able totokenize properly.

*

* @param aIsFinalChunk Whether this is thelast chunk of data that we will

* get to see.

* @param aTokenAllocator The token allocatorto use for this document.

* @return Our success in setting up.

*/

//本操作主要在进行分词操作之前进行操作,这会让我们做一些初始化操作,使分词器能够正常地运行并进行操作

nsresult

nsHTMLTokenizer::WillTokenize(PRBoolaIsFinalChunk,

nsTokenAllocator*aTokenAllocator)

{

mTokenAllocator = aTokenAllocator; //通过参数设置mTokenAllocator

mIsFinalChunk = aIsFinalChunk; //通过参数设置mIsFinalChunk

// Cause ScanDocStructure to search from here for newtokens...

mTokenScanPos = mTokenDeque.GetSize(); //获取TokenDeque的大小,并设置当前位置,也就是说新到来的Token将会从这个位置开始放入队列,也就为后面会介绍到的ScanDocStructrue方法提供了支持。该方法会从此位置往后的Token们进行词法判断。

return NS_OK;

}

/**

* Pushes all of the tokens in aDeque onto thefront of our deque so they

* get processed before any other tokens.

*

* @param aDeque The deque with the tokens init.

*/

//这个方法就是将存在于另一个队列aDeque中的所有Token,按序插入到我们的队列中的最前面

void

nsHTMLTokenizer::PrependTokens(nsDeque&aDeque)

{

PRInt32 aCount = aDeque.GetSize(); //获取aDeque的大小

for (PRInt32 anIndex = 0; anIndex < aCount;++anIndex) { //遍历所有元素,进行插入

CToken* theToken = (CToken*)aDeque.Pop(); //获取当前位置的元素

PushTokenFront(theToken); //插入到当前队列中

}

}

//下面这个方法,是用来将另一个Tokenizer的状态拷贝到当前Tokenizer中来,即相当于还原另一个Tokenizer的解析状态(解析上下文)。这主要是为document.write()所准备的,后面大家可以了解到,这个Javascript的语句导致了很多问题的产生。

/**

* Copies the state flags from aTokenizer intothis tokenizer. This is used

* to pass information around between the maintokenizer and tokenizers

* created for document.write() calls.

*

* @param aTokenizer The tokenizer with moreinformation in it.

* @return NS_OK

*/

//拷贝状态,很简单只需要拷贝Tokenizerz的mFlags即可,这主要是用来在主Tokenizer和被document.write()调用所创建的Tokenizer的之间传递信息的。

nsresult

nsHTMLTokenizer::CopyState(nsITokenizer*aTokenizer)

{

if (aTokenizer) {

mFlags = ((nsHTMLTokenizer*)aTokenizer)->mFlags; //获取该Tokenizer的mFlags

}

return NS_OK;

}

//下面我们会介绍一个ScanDocStructure方法,这是一个文法正确性监测的方法。他会去检查当前已经解析的Tokens中的所有Token,并修正一些文法上的错误,比如

等明显错误的文法。

//不同的浏览器内核中(如Webkit)都对文法错误编写了大量的代码对其进行修正。然而对于同样的文法错误,可能会出现不同的处理,这样会明显导致一些相同的网页在不同的浏览器上出现不同的显示结果。

//然而这并不是检查的全部,比如诸如

  • 等语法错误是不会在这里检查出来的,那些会根据具体的DTD进行不同的区分和检查,我们在后面的文档中会详细解释这一点,目前我们先来看看文法监测的方法。

    //这个文法监测主要是通过一个栈来进行监测的,类似于大多数表达式处理法。在这个方法中我们实际上并不只是将出错的节点标示位进行一下标示,并不删除该节点。

    //首先先是一个为了给文法监测提供支持的方法,这个方法很简单,就是根据给定的Tag名,找到并返回当前Tag栈中的(第一个)符合标准的元素的位置。这个位置可以提供给组件访问方法ObjectAt()来使用。

    /**

    * This is a utilty method forScanDocStructure, which finds a given

    * tag in the stack. The return value is meantto be used with

    * nsDeque::ObjectAt() on aTagStack.

    *

    * @paramaTag -- the ID of the tag we're seeking

    * @paramaTagStack -- the stack to be searched

    * @returnindex position of tag in stack if found, otherwise kNotFound

    */

    staticPRInt32

    FindLastIndexOfTag(eHTMLTags aTag,nsDeque &aTagStack)

    {

    PRInt32 theCount = aTagStack.GetSize(); //首先获取栈的大小

    while (0 < theCount) { //循环从栈顶开始依次遍历栈中的元素

    CHTMLToken* theToken = (CHTMLToken*)aTagStack.ObjectAt(--theCount);

    if (theToken) { //如果获取成功

    eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID(); //获取其类型

    if (theTag == aTag) { //进行判断,如果相等

    return theCount; //那么就返回它的下标

    }

    }

    }

    return kNotFound; //运行到这说明没有找到,则返回404…

    }

    //好了,下面我们就来看真正进行文法监测的ScanDocStructure方法

    /**

    * This method scans the sequence of tokens todetermine whether or not the

    * tag structure of the document is wellformed. In well formed cases, we can

    * skip doing residual style handling and allowinlines to contain block-level

    * elements.

    *

    * @param aFinalChunk Is unused.

    * @return Success (currently, this functioncannot fail).

    */

    //这个方法扫描tokens的队列来决定该文档的结构是否是良构的。良构的情况下,我们可以不考虑其他样式的处理等,inlines等标签包含block级别的元素等问题。

    nsresultnsHTMLTokenizer::ScanDocStructure(PRBool aFinalChunk)

    {

    nsresult result = NS_OK;

    if (!mTokenDeque.GetSize()) { //首先需要判断队列不为空

    return result;

    }

    CHTMLToken* theToken = (CHTMLToken*)mTokenDeque.ObjectAt(mTokenScanPos); //获取当前位置的Token

    // Start by finding the first start tag that hasn't beenreviewed.

    //首先我们需要从当前位置开始,向前寻找到第一个没有被处理过的起始类型标签,如还没有遇到的

  • 标签等,这主要是为了继承上次ScanDocStructure的工作往下做

    while (mTokenScanPos > 0) {

    if (theToken) {

    eHTMLTokenTypes theType =eHTMLTokenTypes(theToken->GetTokenType());

    if (theType == eToken_start && //如果类型为eToken_start,即起始类型标签

    theToken->GetContainerInfo() == eFormUnknown) { //通过GetContainerInfo来判断其是否已经遇到了对应的结束类型标签

    break;

    }

    }

    theToken = (CHTMLToken*)mTokenDeque.ObjectAt(--mTokenScanPos); //寻找下一个标签

    } //如果循环结束还未找到,那么说明从mTokenScanPos开始进行解析就可以

    // Now that we know where to start, let's walk through the

    // tokens to see which are well-formed. Stop when you runout

    // of fresh tokens.

    //现在我们知道了应当从哪里开始进行解析,我们只需要遍历所有的Tokens来看看哪个是良构的即可。循环直到我们没有了新的tokens为止。

    //申请两个栈,数据类型不用那么严格可用nsDeque的数据结构,我们只对其进行栈式操作即可

    nsDeque theStack(0);

    nsDeque tempStack(0);

    PRInt32 theStackDepth = 0;

    // Don't bother if we get ridiculously deep.

    //注意到,如果我们的tag嵌套层数超过了200层,那么我们就不需要再继续进行解析了,直接忽略后面的tag,这也就是说,如果你的HTMl文件中有201个

    ,之后再接201个
    ,那么最后一个
    会直接被忽略掉,因为FireFox最多支持200层嵌套。

    static const PRInt32 theMaxStackDepth = 200;

    while (theToken && theStackDepth

    eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType());

    eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID();

    if (nsHTMLElement::IsContainer(theTag)) { // Bug 54117

    //貌似是为了修正某个bug而推出的,首先设置两个BOOL位来判断其是否是Block或inline元素,主要是为了下面判断该Tag

    PRBool theTagIsBlock =gHTMLElements[theTag].IsMemberOf(kBlockEntity);

    PRBool theTagIsInline = theTagIsBlock

    ? PR_FALSE

    :gHTMLElements[theTag].IsMemberOf(kInlineEntity);

    //判断当前tag是否是inline类,或block类,或

    if (theTagIsBlock || theTagIsInline ||eHTMLTag_table == theTag) {

    switch(theType) {

    case eToken_start: //如果是起始型Token

    {

    //下面这个ShouldVerifyHierarchy方法用来检测该元素是否且不能被包含在同类型的其他元素中

    if(gHTMLElements[theTag].ShouldVerifyHierarchy()) {

    PRInt32 earlyPos =FindLastIndexOfTag(theTag, theStack);

    if(earlyPos != kNotFound) {

    //如果到了此处,说明我们找到了一个不应当被包含的元素。我们需要标记这个错误的父元素,以及该元素下的所有节点为“出错类型”,比如

    ,那么我们需要标记最外层的节点为错误类型节点,以及其所有的子元素全部为错误类型节点。

    //Uh-oh, we've found a tag that is not allowed to nest at

    //all. Mark the previous one and all of its children as

    //malformed to increase our chances of doing RS handling

    //on all of them. We want to do this for cases such as:

    //

    .

    //Note that we have to iterate through all of the chilren

    // of theoriginal malformed tag to protect against:

    //

    ,so that the

    //is allowed to contain the

    .

    //XXX What about , where the second closes

    //the ?

    //需要注意的是,我们必须检查原来错误类型父节点的所有子节点里防止类似

    的情况,因为节点是允许包含
    节点的(而
    节点又允许包含节点),然而它们实际上都处在出现了错误的根节点中。

    nsDequeIterator it(theStack,earlyPos), end(theStack.End());

    //下面我们需要遍历从出错节点位置,直到栈顶的所有元素,并将他们全部标记为eMalformed,即说明该节点的结构有错误

    while(it < end) {

    CHTMLToken*theMalformedToken =

    static_cast(it++);

    theMalformedToken->SetContainerInfo(eMalformed);

    }

    }

    }

    theStack.Push(theToken); //将当前token入栈

    ++theStackDepth; //增加栈的深度

    }

    break;

    //前面我们对开始型节点进行了分析,下面我们将对结束型节点进行处理

    case eToken_end: //判断如果是结束型节点,我们需要寻找它对应的起始节点,如果不出意外的话,该起始节点就应当位于当前的栈顶,否则就说明格式有错误

    {

    CHTMLToken *theLastToken = //获取栈顶元素

    static_cast(theStack.Peek());

    if(theLastToken) { //如果栈顶元素存在

    if(theTag == theLastToken->GetTypeID()) { //找到该节点

    theStack.Pop(); // Yank it for real //注意这里我们真正地将其从栈中移除了

    theStackDepth--; //减低栈的深度

    theLastToken->SetContainerInfo(eWellFormed); //设置其为格式正确

    //This token wasn't what we expected it to be! We need to

    //go searching for its real start tag on our stack. Each

    //tag in between the end tag and start tag must be malformed

    //其他情况下,说明当前栈顶元素并不是我们想要的,我们需要去我们的栈中找到其真正对应的开始型节点,此时在开始和结束节点之间的所有节点实际上此时都应是malformed,即结构有问题的,我们需要将所有这些节点进行设置。(如果该结束型Tag根本没有对应的起始节点的情况是什么处理都不用做,因为结束型节点实际上并不存储,其作用只是“关闭”起始型节点,也就是说不考虑上下文的情况下

    abc
    abc
    的显示是一样的,后者不会导致什么文法错误,后两个
    被自动地忽略了)

    if(FindLastIndexOfTag(theTag, theStack) != kNotFound) {

    //从栈中找到该节点的起始节点,如果能够找到的话我们就将这两个节点进行close并且出栈,并将其路途中的所有元素设置为malformed,并不进行其他操作,以保持文档整体结构不受影响

    //Find theTarget in the stack, marking each (malformed!)

    //tag in our way.

    //将栈顶元素出栈

    theStack.Pop(); // Pop off theLastToken for real.

    do{

    theLastToken->SetContainerInfo(eMalformed); //设置该元素为eMalformed

    //并且将其压入到我们临时设定的栈(其实此处用作队列了)中

    tempStack.Push(theLastToken);

    //取出下一个栈顶元素

    theLastToken = static_cast(theStack.Pop());

    //这样循环,直到找到一个和该end类型节点类型相同的起始节点为止

    } while(theLastToken && theTag != theLastToken->GetTypeID());

    //XXX The above test can confuse two different userdefined

    //tags.

    //判断theLastToken是否为空,如果为空,说明前面出错误了,即虽然findLastIndexOfTag找到了,但是遍历了整个栈却没找到

    NS_ASSERTION(theLastToken,

    "FindLastIndexOfTag lied to us!"

    " We couldn't find theTag on theStack");

    theLastToken->SetContainerInfo(eMalformed);

    //Great, now push all of the other tokens back onto the

    //stack to preserve the general structure of the document.

    //Note that we don't push the target token back onto the

    //the stack (since it was just closed).

    while(tempStack.GetSize() != 0) {

    theStack.Push(tempStack.Pop());

    }

    }

    }

    }

    }

    break;

    default:

    break;

    }

    }

    }

    theToken = (CHTMLToken*)mTokenDeque.ObjectAt(++mTokenScanPos); //获取下一个token

    }

    return result;

    }

    //下面这个方法DidTokenize()是Mozilla经典的三部曲方法中的最后一步Did方法,主要进行一些收尾工作,可见其只是单纯地调用了刚才我们的ScanDocStructure()

    /**

    * This method is called after we're donetokenizing a chunk of data.

    *

    * @param aFinalChunk Tells us if this was thelast chunk of data.

    * @return Error result.

    */

    nsresult

    nsHTMLTokenizer::DidTokenize(PRBoolaFinalChunk)

    {

    return ScanDocStructure(aFinalChunk);

    }

    //下面的ConsumeToken方法则是真正的对Token进行处理的方法,即其将调用Scanner,判断接下来的Token是什么类型的Token,并且调用处理相应Token类型的处理函数进行处理。

    /**

    * This method is repeatedly called by thetokenizer.

    * Each time, we determine the kind of tokenwe're about to

    * read, and then we call the appropriatemethod to handle

    * that token type.

    *

    * @paramaScanner The source of our input.

    * @paramaFlushTokens An OUT parameter to tell the caller whether it should

    * process our queued tokensup to now (e.g., when we

    * reach a

    你可能感兴趣的:(c/c++,数据结构与算法,javascript)