`
yuanlanxiaup
  • 浏览: 859110 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

Mozilla FireFox Gecko内核源代码解析

(1.nsTokenizer)

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

信息抽取小组

耿耘

gengyun@sohu.com

前面我们大体介绍了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<nsIHTMLContentSink> sink = //这种构建方法需要了解

do_QueryInterface(const_cast<nsIContentSink*>(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,并修正一些文法上的错误,比如<div><p></div></p>等明显错误的文法。

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

//然而这并不是检查的全部,比如诸如<table><li/></table>等语法错误是不会在这里检查出来的,那些会根据具体的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.

//首先我们需要从当前位置开始,向前寻找到第一个没有被处理过的起始类型标签,如还没有遇到</li>的<li>标签等,这主要是为了继承上次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个<div>,之后再接201个</div>,那么最后一个<div></div>会直接被忽略掉,因为FireFox最多支持200层嵌套。

static const PRInt32 theMaxStackDepth = 200;

while (theToken && theStackDepth <theMaxStackDepth) {

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类,或<table>

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) {

//如果到了此处,说明我们找到了一个不应当被包含的元素。我们需要标记这个错误的父元素,以及该元素下的所有节点为“出错类型”,比如<a><div><a></a></div></a>,那么我们需要标记最外层的<a>节点为错误类型节点,以及其所有的子元素全部为错误类型节点。

//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:

//<a><div><a></a></div></a>.

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

// of theoriginal malformed tag to protect against:

//<a><font><div><a></a></div></font></a>,so that the <font>

//is allowed to contain the <div>.

//XXX What about <a><span><a>, where the second <a>closes

//the <span>?

//需要注意的是,我们必须检查原来错误类型父节点的所有子节点里防止类似<a><font><div><a></a></div></font></a>的情况,因为<font>节点是允许包含<div>节点的(而<div>节点又允许包含<a>节点),然而它们实际上都处在出现了错误的根节点中。

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

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

while(it < end) {

CHTMLToken*theMalformedToken =

static_cast<CHTMLToken*>(it++);

theMalformedToken->SetContainerInfo(eMalformed);

}

}

}

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

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

}

break;

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

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

{

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

static_cast<CHTMLToken*>(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根本没有对应的起始节点的情况是什么处理都不用做,因为结束型节点实际上并不存储,其作用只是“关闭”起始型节点,也就是说不考虑上下文的情况下<div>abc</div></div></div>和<div>abc</div>的显示是一样的,后者不会导致什么文法错误,后两个</div>被自动地忽略了)

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<CHTMLToken*>(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 <script>).

* @return Success or error

*/

//这个方法会被tokenizer多次地调用(其实是被Parser多次调用吧),每次调用的时候,我们都会对即将读取的token进行判断,判断其类型并调用相应的处理函数来进行处理。

nsresult

nsHTMLTokenizer::ConsumeToken(nsScanner&aScanner, PRBool& aFlushTokens)

{

PRUnichar theChar;

CToken* theToken = nsnull;

nsresult result = aScanner.Peek(theChar); //首先查看扫描器的当前字符

switch(result) { //对查看结果进行判断

case kEOF: //如果是文件末尾了

// Tell our caller that'we finished.

return result; //直接返回

case NS_OK: //如果成功以及默认情况下(这个地方就两种情况用if-else多好)

default:

if (!(mFlags &NS_IPARSER_FLAG_PLAIN_TEXT)) { //当前解析模式不是plaintext时

if (kLessThan == theChar) { //如果当前字符为’<’

//说明接着是一个tag,我们调用ConsumeTag

return ConsumeTag(theChar, theToken,aScanner, aFlushTokens);

} else if(kAmpersand == theChar) { //如果当前字符为’&’

//说明接着是一个entity,我们调用ConsumeEntity

return ConsumeEntity(theChar, theToken,aScanner);

}

}

if (kCR == theChar || kLF == theChar) {//如果当前字符是’\r’或’\n’

return ConsumeNewline(theChar, theToken,aScanner); //说明是个新行,我们调用ConsumeNewline对其进行解析

} else {

if (!nsCRT::IsAsciiSpace(theChar)) { //判断该字符如果不是Accii的空格字符

if (theChar != '\0'){ //如果该字符不为’\0’空字符,那么说明是一个text文本

result = ConsumeText(theToken,aScanner); //调用ConsumeText对其进行处理

} else { //其他情况下,即\0’’

// Skipthe embedded null char. Fix bug 64098.

aScanner.GetChar(theChar); //跳过当前字符,获取下一个字符

}

break;

}

result = ConsumeWhitespace(theChar, theToken, aScanner); //运行到这说明thechar为Accii标准的空格字符,运行ConsumeWhitespace对其进行处理

}

break;

}

return result;

}

//看完了具体的分词流程的控制操作,我们就来看看在刚才的操作过程中所用到的几个具体进行特定元素处理的方法如consumeTag,consumeEntity等。

//首先我们来看对于tag标签进行处理的consumeTag方法

/**

* This method is called just after a"<" has been consumed

* and we know we're at the start of some kindof tagged

* element. We don't know yet if it's a tag ora comment.

*

* @paramaChar is the last char read

* @paramaToken is the out arg holding our new token (the function allocates

* the return token usingmTokenAllocator).

* @paramaScanner represents our input source

* @paramaFlushTokens is an OUT parameter use to tell consumers to flush

* the current tokens afterprocessing the current one.

* @returnerror code.

*/

//这个方法会在分词器遇到’<’字符的时候被调用,此时我们知道我们处于某种tag元素的起始端。但是此时我们还不知道他是一个普通的tag,还是一段注释。

nsresult

nsHTMLTokenizer::ConsumeTag(PRUnicharaChar,

CToken*&aToken,

nsScanner&aScanner,

PRBool&aFlushTokens)

{

PRUnichar theNextChar, oldChar; //申请两个变量

nsresult result = aScanner.Peek(aChar, 1); //此时的0位置应当为’<’,我们察看’<’之后的1位置的字符放到aChar中

if (NS_OK == result) { //如果获取成功

switch (aChar) { //根据获取来的字符进行判断

case kForwardSlash: //如果是’/’

result = aScanner.Peek(theNextChar, 2); //获取2位置的字符,即’/’ 之后的下一个字符,放到theNextChar中备用

if (NS_OK == result) { //如果获取成功

// Get the original "<" (we'vealready seen it with a Peek)

aScanner.GetChar(oldChar); //首先通过Get获取处于当前位置的’<’(我们之前已经通过peek操作知道它的值了)注意这个和察看操作peek不同,这个会将该char出队并放入oldChar中

// XML allows non ASCII tag names, consumethis as an end tag. This

// is needed to make XML view source work

//XML允许非ASCII字符的tag名,我们将这种tag作为结束型Tag来处理。这主要是为了让XML观看源代码模式能够正常工作

PRBool isXML = !!(mFlags & NS_IPARSER_FLAG_XML); //首先判断是否是XML模式,注意这个!!,我没搞清为什么要这么写

if (nsCRT::IsAsciiAlpha(theNextChar) || //判断theNextChar是否是普通Alpha字符,即(A-Z或a-z),或’>’,或当前是XML模式且theNextChar是非Ascii字符

kGreaterThan == theNextChar ||

(isXML &&!nsCRT::IsAscii(theNextChar))) {

result = ConsumeEndTag(aChar,aToken, aScanner);

} else {

result = ConsumeComment(aChar,aToken, aScanner);

}

}

break;

case kExclamation: //如果是’!’符号

result = aScanner.Peek(theNextChar, 2); //同样获取’!’下一个位置的字符

if (NS_OK == result) {

// Get the original "<" (we'vealready seen it with a Peek)

aScanner.GetChar(oldChar); //同样获取’<’

if (kMinus == theNextChar ||kGreaterThan == theNextChar) {

//如果是’-’符号或者’>’符号

result = ConsumeComment(aChar,aToken, aScanner); //说明是一个注释型的tag,我们调用ConsumeComment对其进行处理

} else {

//其他情况下,说明是某种特殊的标签如DOCTYPE等,我们调用ConsumeSpecialMarkup对其进行处理

result =ConsumeSpecialMarkup(aChar, aToken, aScanner);

}

}

break;

case kQuestionMark: //如果是’?’字符,说明一定是某种处理指令

// It must be a processing instruction...

// Get the original "<" (we'vealready seen it with a Peek)

aScanner.GetChar(oldChar); //同样获取’<’

result = ConsumeProcessingInstruction(aChar, aToken, aScanner); //直接调用处理指令的ConsumeProcessingInstruction对其进行处理

break;

default:

// XML allows non ASCII tag names, consumethis as a start tag.

//XML允许非ASCII字符的tag名,将这个tag作为起始型的tag进行处理

PRBool isXML = !!(mFlags & NS_IPARSER_FLAG_XML); //首先判断是否是XML模式

if (nsCRT::IsAsciiAlpha(aChar) || //如果当前字符是ascii字符或者当前模式是XML模式且当前字符不是ascii字符

(isXML && !nsCRT::IsAscii(aChar))){

// Get the original "<" (we'vealready seen it with a Peek)

aScanner.GetChar(oldChar); //还是得先获取’<’

//之后将其作为起始型标签,调用ConsumeStartTag进行处理

result = ConsumeStartTag(aChar, aToken, aScanner, aFlushTokens);

} else {

// We are not dealing with a tag. So, don'tconsume the original

// char and leave the decision toConsumeText().

//此时,我们不认为是在处理一个tag。因此不要获取原来的’<’,而是将他们都作为普通text,调用ConsumeText进行处理

result = ConsumeText(aToken, aScanner);

}

}

}

// Last ditch attempt to make sure we don't lose data.

if (kEOF == result &&!aScanner.IsIncremental()) { //最后如果当前字符已是文件末尾,且扫描器并不是incremental模式,我们需要确保不丢失任何数据

// Whoops, we don't want to lose any data!Consume the rest as text.

// This normally happens for either a trailing < or </

//将剩下的数据都作为text进行处理,这种情况一般在碰到<或者</的时候会出现,我感觉剩下的一般都应该是空数据了吧

result = ConsumeText(aToken, aScanner);

}

return result;

}

//下面是一个很经典的,对HTML TAG的属性进行处理的方法,它主要在对起始型Tag或者结束型Tag进行处理的时候被调用。

/**

* This method is called just after we'veconsumed a start or end

* tag, and we now have to consume itsattributes.

*

* @paramaChar is the last char read

* @paramaToken is the start or end tag that "owns" these attributes.

* @paramaScanner represents our input source

* @returnError result.

*/

//这个方法通常在我们刚刚处理了一个起始型或者结束型的标签后会被调用,这时我们想做的就是处理它的所有属性

nsresult

nsHTMLTokenizer::ConsumeAttributes(PRUnicharaChar,

CToken*aToken,

nsScanner& aScanner)

{

//设置几个变量

PRBool done = PR_FALSE;

nsresult result = NS_OK;

PRInt16 theAttrCount = 0;

//获取当前的TokenAllocator()

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

while (!done && result == NS_OK) { //循环

CAttributeToken* theToken = //使用tokenAllocator,创建一个unknown类型的attribute token

static_cast<CAttributeToken*>

(theAllocator->CreateTokenOfType(eToken_attribute,

eHTMLTag_unknown));

if (NS_LIKELY(theToken != nsnull)) { //创建成功

// Tell the new token to finish consumingtext...

result = theToken->Consume(aChar, aScanner, mFlags); //调用CAttributeToken的特殊的Consume方法,具体的我们会在具体的Token分析中再说,这里就是解析并获取该tag的一个属性,并将其放置到theToken中

if (NS_SUCCEEDED(result)) {

++theAttrCount; //将属性的数量加一

AddToken((CToken*&)theToken, result, &mTokenDeque,theAllocator); //将theToken放入队列中

} else { //其他情况下,即theToken分配失败

IF_FREE(theToken, mTokenAllocator); //释放theToken

// Bad attribute returns shouldn't propagateout.

//我们不希望坏的属性影响其他地方,即不希望其传播出去

if (NS_ERROR_HTMLPARSER_BADATTRIBUTE ==result) { //因此要做一下判断,如果为坏属性

result = NS_OK; //那么手动将result更改为NS_OK

}

}

}

else {

result = NS_ERROR_OUT_OF_MEMORY; //如果Allocator分配失败,那么说明没有内存了,返回没有内存错误

}

#ifdefDEBUG //输出调试信息

if (NS_SUCCEEDED(result)) {

PRInt32 newline = 0;

aScanner.SkipWhitespace(newline);

NS_ASSERTION(newline == 0,

"CAttribute::Consume() failed tocollect all the newlines!");

}

#endif

if (NS_SUCCEEDED(result)) { //判断,如果结果为成功

result = aScanner.Peek(aChar); //查看位置的当前字符串

if (NS_SUCCEEDED(result)) { //首先要判断当前字符串查看成功

if (aChar == kGreaterThan) { // You just ate the '>' //如果是’>’

aScanner.GetChar(aChar); // Skip the '>' //那么获取该’>’字符

done = PR_TRUE; //设置循环条件done为true,说明我们已经解析到了该tag的末尾,所有tag的属性都已经转换为token并入队,可以退出循环了

} else if(aChar == kLessThan) { //其他情况下如果遇到了’<’

aToken->SetInError(PR_TRUE); //那么说明当前token出现了错误

done = PR_TRUE; //设置循环条件,退出循环

}

}

}

}

if (NS_FAILED(result)) { //如果是因为result为False而退出的循环

aToken->SetInError(PR_TRUE); //设置当前Token为错误

if (!aScanner.IsIncremental()) { //如果当前Scanner的增量标示位为不为真,即当前的buff中可能有空数据

result = NS_OK; //手动更改结果为OK

}

}

aToken->SetAttributeCount(theAttrCount); //设置一下aToken记录属性数量的变量

return result; //返回解析结果

}

//下面这个方法,主要是针对起始型Tag进行的解析,该方法会处理该starttag及其所有的属性,生成一个start型的token,以及一系列其对应的属性token。

/**

* This method consumes a start tag and all ofits attributes.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token. (allocated

*by the function using mTokenAllocator

* @param aScanner Our source of data

* @param aFlushTokens is an OUT parameter useto tell consumers to flush

* the current tokens afterprocessing the current one.

* @return Error result.

*/

{

// Remember this for later in case you have to unwind...

//为了将来回溯的可能提供支持,只需记录一下当前的队列长度即可

PRInt32 theDequeSize = mTokenDeque.GetSize();

nsresult result = NS_OK;

//获取当前的TokenAllocator

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

//用Allocator生成一个start类型的标签

aToken = theAllocator->CreateTokenOfType(eToken_start, eHTMLTag_unknown);

NS_ENSURE_TRUE(aToken, NS_ERROR_OUT_OF_MEMORY); //确保生成成功

// Tell the new token to finish consuming text...

//调用该Token的Consume方法,对starttag的tagname进行解析,这个解析的原理很简单,就是从当前位置开始持续解析直到遇到了一个’>’或者空格’ ’为止

result = aToken->Consume(aChar, aScanner, mFlags);

if (NS_SUCCEEDED(result)) { //如果解析成功

//添加Token到队列中

AddToken(aToken, result, &mTokenDeque, theAllocator);

//获取该Token的Tag名

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

// Good. Now, let's see if the next char is">".

// If so, we have a complete tag, otherwise, we haveattributes.

//现在,我们要来看看下一个字符是否是’>’,如果是的话,我们就得到了一个完整的tag,其他情况下,我们则认为后面跟随的是属性值

result= aScanner.Peek(aChar); //查看一下当前字符的值

if (NS_FAILED(result)) { //如果失败

aToken->SetInError(PR_TRUE); //设置aToken为有错误

// Don't return early here so we can create atext and end token for

// the special <iframe>, <script>and similar tags down below.

result = NS_OK;

} else {

if (kGreaterThan != aChar) { // Look for a '>' //判断是否是’>’符号

result = ConsumeAttributes(aChar, aToken, aScanner); //如果不是,说明后面还有属性值,那么就对属性进行处理

} else {

aScanner.GetChar(aChar); //获取当前字符,放到aChar中去

}

}

/* Nowthat that's over with, we have one more problem to solve.

In the case that we just read a<SCRIPT> or <STYLE> tags, we should go and

consume all the content itself.

But XML doesn't treat these tagsdifferently, so we shouldn't if the

document is XML.

*/

//现在,我们已经完成了标准意义上的解析了,但是还有一个问题需要解决。如果我们刚才读取的是<SCRIPT>或者<STYLE>型的tag的话,我们应当尝试去处理其所有的内容(即将他们全部读入)

//但是XML模式下我们对这些tags没有做什么不同的处理,因此如果是XML模式,我们则不会这样去读取

if (NS_SUCCEEDED(result) &&!(mFlags & NS_IPARSER_FLAG_XML)) {

//CDATA,其实这里并不一定是读取了<![CDATA>标签,因此首先我们先要记录一下这两个标签是否能够包含kCDATA型数据

PRBool isCDATA = gHTMLElements[theTag].CanContainType(kCDATA);

PRBool isPCDATA = eHTMLTag_textarea == theTag || //未找到这两个应当是常量的定义,也许是被ifdef掉了, VC2008找不到这种ifdef的变量,应当是字符数组形式的”textarea”和”title”

eHTMLTag_title == theTag;

// XXX This is an evil hack, we should be ableto handle these properly

// in the DTD.

//这里是一个不太对的处理方式,我们应当能够在DTD中进行这些处理

if((eHTMLTag_iframe == theTag &&

(mFlags &NS_IPARSER_FLAG_FRAMES_ENABLED)) ||

(eHTMLTag_noframes == theTag &&

(mFlags &NS_IPARSER_FLAG_FRAMES_ENABLED)) ||

(eHTMLTag_noscript == theTag &&

(mFlags &NS_IPARSER_FLAG_SCRIPT_ENABLED)) ||

(eHTMLTag_noembed == theTag)) { //如果theTag为”iframe”或”noframes”或者”noscript”或”noembed”且相应的标示位为开启窗台

isCDATA = PR_TRUE; //那么同样设置isCDATA为true

}

// Plaintext contains CDATA, but it's special,so we handle it

// differently than the other CDATA elements

//普通文本类型的节点同样也包含CDATA,但是它很特殊,因此我们对其进行与其他CDATA元素不同的处理

if (eHTMLTag_plaintext == theTag) { //如果theTag节点为plaintext

isCDATA = PR_FALSE; //设置isDATA为PR_FALSE

// Note: We check in ConsumeToken() for thisflag, and if we see it

// we only construct text tokens (which iswhat we want).

//设置相应的plaintext标示位,注意我们在ConsumeToken中会对这个位进行检查,并且如果我们看到它我们只会构建相应的text token(因为这正是我们想要的)

mFlags |= NS_IPARSER_FLAG_PLAIN_TEXT;

}

//首先对isCDATA或isPCDATA进行判断

if (isCDATA || isPCDATA) {

//设置标示为done,默认为PR_FALSE

PRBool done = PR_FALSE;

//设置一个字符串endTagName,设置为theTag的字符串型值

nsDependentString endTagName(nsHTMLTags::GetStringValue(theTag));

//创建一个新的CToken,并且创建其为eToken_text类型

CToken* text =

theAllocator->CreateTokenOfType(eToken_text, eHTMLTag_text);

//确保创建成功

NS_ENSURE_TRUE(text, NS_ERROR_OUT_OF_MEMORY);

//将其强制转换为CTextToken类型

CTextToken* textToken = static_cast<CTextToken*>(text);

//如果是CDATA

if (isCDATA) {

//我们使用CTextToken的ConsumeCharacterData对后面的数据进行处理并获取所有的属性等

result = textToken->ConsumeCharacterData(theTag != eHTMLTag_script,

aScanner,

endTagName,

mFlags,

done);

// Only flush tokens for <script>, togive ourselves more of a

// chance of allowing inlines to containblocks.

//只有在theTag为<Script>标签的时候,才设置aFlushTokens位(并且还得用done做一下判断)

aFlushTokens = done && theTag == eHTMLTag_script;

} else if(isPCDATA) {

//如果是PCDATA,即位textarea或title

// Title is consumed conservatively in orderto not regress

// bug 42945

//调用CTextToken的CosumeParsedCharacterData来对其进行处理,这两个方法的区别,我们放到HTMLTokens解析的时候再进行,这里只需要知道他们分别进行不同的解析即可

result = textToken->ConsumeParsedCharacterData(

theTag ==eHTMLTag_textarea,

theTag == eHTMLTag_title,

aScanner,

endTagName,

mFlags,

done);

// Note: we *don't* set aFlushTokens here.

//注意到,这里我们就不需要设置aFlushTokens了(因为没有<script>)

}

// We want to do this unless result is kEOF,in which case we will

// simply unwind our stack and wait for moredata anyway.

//下面这段代码只有在result为kEOF的时候才不会运行,而在这种时候我们只需要不做任何操作等待更多的数据到达即可

if (kEOF != result) {

//将名为text的Token加入到队列中去

AddToken(text, NS_OK, &mTokenDeque, theAllocator);

//清空endToken

CToken* endToken = nsnull;

//判断如果result为成功且done

if (NS_SUCCEEDED(result) &&done) { //那么我们下面要继续获取它的结束tag

PRUnichar theChar;

// Getthe <

result = aScanner.GetChar(theChar); //获取’<’,读者可以思考一下,此处的空格是如何处理的?

//做判断,如果此处不为’<’,那么说明出错误了,报错

NS_ASSERTION(NS_SUCCEEDED(result)&& theChar == kLessThan,

"CTextToken::Consume*Data is broken!");

#ifdefDEBUG //调试信息,暂且不管了

// Ensure we have a /

PRUnichar tempChar; // Don't change non-debug vars in debug-onlycode

result = aScanner.Peek(tempChar);

NS_ASSERTION(NS_SUCCEEDED(result)&& tempChar == kForwardSlash,

"CTextToken::Consume*Datais broken!");

#endif

//对结束型Tag进行处理

result = ConsumeEndTag(PRUnichar('/'), endToken, aScanner);

if(!(mFlags & NS_IPARSER_FLAG_VIEW_SOURCE) &&

NS_SUCCEEDED(result)) {

// IfConsumeCharacterData returned a success result (and

//we're not in view source), then we want to make sure that

//we're going to execute this script (since the result means

// thatwe've found an end tag that satisfies all of the right

//conditions).

//此处说明,如果ConsumeCharacterData返回了成功值(并且我们不在view source模式中),那么我们希望确保我们马上就处理这段脚本(因为result成功说明我们已经找到了对应的end tag并且一切解析正常)

endToken->SetInError(PR_FALSE); //设置endToken的inError为FALSE,即设置其状态为正常

}

//其他情况下,如果当时相应Consuem的result为kFakeEndTag,这个需要到HTMLTokens中去进行解析,并且当前模式不为view source模式

} else if(result == kFakeEndTag &&

!(mFlags &NS_IPARSER_FLAG_VIEW_SOURCE)) {

result = NS_OK; //手动将result设置为NS_OK

endToken =theAllocator->CreateTokenOfType(eToken_end, theTag,

endTagName);

//创建一个结束型的标签,并将其加入到队列中去

AddToken(endToken, result,&mTokenDeque, theAllocator);

if(NS_LIKELY(endToken != nsnull)) { //如果判断结果标签不为空

endToken->SetInError(PR_TRUE); //设置endToken状态为正常

}

else{ //其他情况下说明没有内存AddToken里对endToken的状态做了判断

result = NS_ERROR_OUT_OF_MEMORY;

}

} else if(result == kFakeEndTag) {

// If weare here, we are both faking having seen the end tag

// andare in view-source.

result = NS_OK; //强制手动将resutl设置为OK

}

} else {

IF_FREE(text, mTokenAllocator); //其他情况下,说明当前是EOF,什么都不用做,直接释放text的Token,并且等待新数据的到达即可

}

}

}

// This code is confusing, so pay attention.

// If you're here, it's because we were in themidst of consuming a start

// tag but ran out of data (not in the stream,but in this *part* of the

// stream. For simplicity, we have to unwindour input. Therefore, we pop

// and discard any new tokens we've queuedthis round. Later we can get

// smarter about this.

//这段代码比较容易让人迷惑。判断result,如果为失败,说明我们位于解析一个start tag的中间,但是没有数据了(并不是stream中没有数据了,而是这一小部分的stream中没有数据了。为了简单,我们只需要等待我们的数据。因此,我们出栈并撤销任何在这个回合中新建的tokens。以后希望对此进行改进)

//这里说明一下,首先欲了解这段代码需要对前面的处理进行详细地了解,可以理解到,对于大多数result为FAILED的情况前面都进行了处理,并手动将其设置为NS_OK,但是如果到了这里,只有几种可能:比如,当读取完了’<TagName‘的时候,我们认为之后应当是来属性,然而我们发现来自Scanner的GetChar失败了,那么说明后面的数据还没有到达,此时我们需要取消掉相应的Token,即’<TagName‘并等待后面的数据到达后再重新进行解析。

if (NS_FAILED(result)) {

while(mTokenDeque.GetSize()>theDequeSize) { //还记得我们最开始记录的theDequeSize嘛?

CToken* theToken = (CToken*)mTokenDeque.Pop(); //将theDequeSize之后多余的Token都出栈并销毁

IF_FREE(theToken, mTokenAllocator);

}

}

} else {

IF_FREE(aToken, mTokenAllocator);

}

return result;

}

//以上就是对StartTag进行处理的方法,通过分析我们可以看到,它对于一般的标签,只需要对其TagName进行读取,而后调用CosumeAttributes对其的属性进行处理即可。而对于类似<Script><STYLE>等标签,则需要对其内部的数据一次性读取完,即CDATA模式。并且最后,还需要对数据的完整性进行判断,如果不完整还需要进行回溯。

//下面,我们来看和处理StartTag对应的,对EndTag进行处理的方法。

/**

* This method consumes an end tag and any"attributes" that may come after it.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result

*/

nsresult

nsHTMLTokenizer::ConsumeEndTag(PRUnicharaChar,

CToken*&aToken,

nsScanner&aScanner)

{

// Get the "/" (we've already seen it with aPeek)

//获取当前的’/’(实际上在调用这个方法之前我们应经通过Peek方法看到它了,否则也进不来这个方法)

aScanner.GetChar(aChar);

//标准地构件化方法,通过TokenAllocator创建end型的aToken

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

aToken = theAllocator->CreateTokenOfType(eToken_end,eHTMLTag_unknown);

NS_ENSURE_TRUE(aToken, NS_ERROR_OUT_OF_MEMORY);

// Remember this for later in case you have to unwind...

//和处理StartTag时一样,记录一下Deque的Size这个数据,以便将来进行回溯

PRInt32 theDequeSize = mTokenDeque.GetSize();

nsresult result = NS_OK;

// Tell the new token to finish consuming text...

//调用endToken的Consume方法对数据进行处理,并且将其添加至队列

result = aToken->Consume(aChar, aScanner, mFlags);

AddToken(aToken, result, &mTokenDeque, theAllocator);

if (NS_FAILED(result)) { //如果添加结果为失败

// Note that this early-return here is safebecause we have not yet

// added any of our tokens to the queue(AddToken only adds the token if

// result is a success), so we don't need to fall through.

//直接返回报错。这个方法是安全的,因为在AddToken中需要对result进行判断,如果失败则不会对队列进行添加

return result;

}

result = aScanner.Peek(aChar); //获取当前位置的字符

if (NS_FAILED(result)) { //如果获取失败

aToken->SetInError(PR_TRUE); //设置aToken状态为异常

// Note: We know here that the scanner is notincremental since if

// this peek fails, then we've already maskedover a kEOF coming from

// the Consume() call above.

//如果到达了这里,我们已经知道scanner不是增量式的了,因为如果Peek的操作失败了,那么我们就知道前面的Consume()调用已经处理过一个kEOF了。

return NS_OK;

}

if (kGreaterThan != aChar) { //如果不为’>’,则获取后面的属性(结束型标签也能有属性么?)

result = ConsumeAttributes(aChar, aToken, aScanner);

} else { //获取当前位置的字符

aScanner.GetChar(aChar);

}

// Do the same thing as we do in ConsumeStartTag.Basically, if we've run

// out of room in this *section* of the document, pop allof the tokens

// we've consumed this round and wait for more data.

//和前面处理ConsumeStartTag一样,如果数据不完整,那么就进行回溯

if (NS_FAILED(result)) {

while (mTokenDeque.GetSize() >theDequeSize) {

CToken* theToken = (CToken*)mTokenDeque.Pop();

IF_FREE(theToken, mTokenAllocator);

}

}

return result;

}

//对于一般的HTML来讲,一些诸如’<’,’>’等符号都属于特殊字符,而在一些TextNode中,这些符号都必须用通配符来进行替换,如&nbsp和&#160等,这些字符又叫做HTML Entity。下面这个方法就是对这些Entity字符进行处理。

/**

* Thismethod is called just after a "&" has been consumed

* andwe know we're at the start of an entity.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

//这个方法只有在遇到了’&’之后才会被处理,并且我们知道我们此时位于一个实体的开始处了

nsresult

nsHTMLTokenizer::ConsumeEntity(PRUnicharaChar,

CToken*&aToken,

nsScanner&aScanner)

{

//首先获取当前位置的字符,放到新变量theChar中

PRUnichar theChar;

nsresult result = aScanner.Peek(theChar, 1);

//获取当前Tokenizer的TokenAllocator

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

if (NS_SUCCEEDED(result)) { //判断,如果获取字符成功

if (nsCRT::IsAsciiAlpha(theChar) ||theChar == kHashsign) { //判断如果当前字符为普通字母或者’#’

//那么则为其创建一个entity类型的Token

aToken = theAllocator->CreateTokenOfType(eToken_entity,eHTMLTag_entity);

//如果创建失败,那么报没有内存错误

NS_ENSURE_TRUE(aToken, NS_ERROR_OUT_OF_MEMORY);

//调用该EntityToken的Consume方法,对该字符进行处理

result = aToken->Consume(theChar, aScanner, mFlags);

if (result == NS_HTMLTOKENS_NOT_AN_ENTITY){ //对结果进行判断,如果为NOT_AN_ENTITY

IF_FREE(aToken, mTokenAllocator); //那么就释放该Token

} else {

if (result == kEOF &&!aScanner.IsIncremental()) { //如果结果为kEOF且Scanner不为增量式的

//那么强制将result设置为OK,此处已经是文件末尾了,是不是Entity就无所谓了

result = NS_OK; // Use as much of the entityas you can get.

}

//添加Token到队列中去

AddToken(aToken, result, &mTokenDeque, theAllocator);

return result;

}

}

// Oops, we're actually looking at plain text...

//此处说明,&后面紧跟的既不是普通字符,也不是#符号,那么说明这不是一个Entity字符,我们需要将其作为普通文本来进行处理

result = ConsumeText(aToken, aScanner);

} else if(result == kEOF && !aScanner.IsIncremental()) {

// If the last character in the file is an &, consume itas text.

//此处说明,当前字符虽然是&,但是位于文件的末尾且aScanner不为增量式的,因此我们只需要将其作为普通文本来进行处理即可

result = ConsumeText(aToken, aScanner); //调用普通文本处理方法对其进行处理

if (aToken) { //如果aToken不为空

aToken->SetInError(PR_TRUE); //那么设置其状态为错误

}

}

return result; //返回结果

}

//下面,我们对空格字符进行处理,只有当我们遇到了一个空格字符后,我们才会对其进行这个方法的处理

/**

* Thismethod is called just after whitespace has been

*consumed and we know we're at the start a whitespace run.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

nsresult

nsHTMLTokenizer::ConsumeWhitespace(PRUnicharaChar,

CToken*&aToken,

nsScanner& aScanner)

{

// Get the whitespace character

//获取第一个空格字符

aScanner.GetChar(aChar);

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

//创建空格类型的Token

aToken = theAllocator->CreateTokenOfType(eToken_whitespace,

eHTMLTag_whitespace);

nsresult result = NS_OK; //首先设置一个result,默认值为NS_OK

if (aToken) { //如果aToken创建成功

result = aToken->Consume(aChar, aScanner, mFlags); //调用其对空格字符进行处理

AddToken(aToken, result, &mTokenDeque, theAllocator); //并将其加入到队列之中

}

return result; //返回结果

}

//下面是对注释型标签进行的处理,只有在遇到了’<!’并且我们知道当前是处在注释标签的起始处时才会进行。

/**

* Thismethod is called just after a "<!" has been consumed

* andwe know we're at the start of a comment.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

nsresult

nsHTMLTokenizer::ConsumeComment(PRUnicharaChar,

CToken*&aToken,

nsScanner&aScanner)

{

// Get the "!"

//获取当前位置的”!”

aScanner.GetChar(aChar);

//获取TokenAllocator,并创建一个comment类型的Token

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

aToken = theAllocator->CreateTokenOfType(eToken_comment,eHTMLTag_comment);

nsresult result = NS_OK;

if (aToken) { //如果创建成功

result = aToken->Consume(aChar, aScanner, mFlags); //调用该commentToken的Consume对后续Text进行处理

AddToken(aToken, result, &mTokenDeque, theAllocator); //调用AddToken将其加至队列中去

}

if (kNotAComment == result) { //如果结果为kNotComment,即其不符合注释标签的格式

// AddToken has IF_FREE()'d our token, so...

result = ConsumeText(aToken, aScanner); //则将其作为普通Text进行处理

}

return result;

}

//下面的方法,是对普通文本进行的处理方法:

/**

* This method is called just after a knowntext char has

* been consumed and we should read a text run.Note: we actually ignore the

* first character of the text run so that wecan consume invalid markup

* as text.

*

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

//我们只有在遇到了文本字符的时候才会去调用这个方法。注意到,我们实际上会忽略第一个字符,这样我们就可以将一些非法的标签等全部作为字符进行处理了。

nsresult

nsHTMLTokenizer::ConsumeText(CToken*&aToken, nsScanner& aScanner)

{

nsresult result = NS_OK; //设置结果为NS_OK

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

CTextToken* theToken = //创建text型Token

(CTextToken*)theAllocator->CreateTokenOfType(eToken_text,eHTMLTag_text);

if (theToken) { //如果创建成功

PRUnichar ch = '\0'; //新建一个字符,初始化’\0’

result = theToken->Consume(ch, aScanner, mFlags); //调用TextToken进行Consume

if (NS_FAILED(result)) { //如果Consume结果失败

if (0 == theToken->GetTextLength()) { //如果当前的文本长度为0

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

aToken = nsnull;

} else {

result = NS_OK; //其他情况设置返回值为NS_OK

}

}

aToken = theToken; //拷贝theToken给aToken

AddToken(aToken, result, &mTokenDeque, theAllocator); //将其添加至队列

}

return result;

}

//下面的方法,是用来处理一些特殊标签的,如<!DOCTYPE等,它只有在遇到<!的时候才会被调用,注意区分它和前面对注释类型标签进行处理的ConsumeComment方法。

/**

* This method is called just after a"<!" has been consumed.

* NOTE: Here we might consume DOCTYPE and"special" markups.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

nsresult

nsHTMLTokenizer::ConsumeSpecialMarkup(PRUnicharaChar,

CToken*& aToken,

nsScanner&aScanner)

{

// Get the "!"

aScanner.GetChar(aChar); //获取当前位置的’!’字符

nsresult result = NS_OK;

nsAutoString theBufCopy;

aScanner.Peek(theBufCopy, 20); //获取前20字节的字符串(为啥是20?)

ToUpperCase(theBufCopy); //全部转换为答谢

PRInt32theIndex = theBufCopy.Find("DOCTYPE",PR_FALSE, 0, 0); //在这20个字符中寻找DOCTYPE字符

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

if (theIndex == kNotFound) { //如果没找到

if ('['== theBufCopy.CharAt(0)) { //那么看一下接下来紧接着的是不是’[’符号,如果是,那么说明是<![CDATA]型标签

//因此我们创建一个cdatasection类型的标签

aToken = theAllocator->CreateTokenOfType(eToken_cdatasection,

eHTMLTag_comment);

//其他情况下,我们分别根据它开头是否是ELEMENT,ATTLIST,ENTITY,NOTATION来判断其是否是一个markupDecl声明型标签,并创建该类型的标签

} else if(StringBeginsWith(theBufCopy, NS_LITERAL_STRING("ELEMENT"))||

StringBeginsWith(theBufCopy,NS_LITERAL_STRING("ATTLIST")) ||

StringBeginsWith(theBufCopy,NS_LITERAL_STRING("ENTITY")) ||

StringBeginsWith(theBufCopy,NS_LITERAL_STRING("NOTATION"))) {

aToken = theAllocator->CreateTokenOfType(eToken_markupDecl,

eHTMLTag_markupDecl);

} else { //否则只能说明它是注释型标签了

aToken = theAllocator->CreateTokenOfType(eToken_comment,

eHTMLTag_comment);

}

} else { //如果找到了DOCTYPE,那么我们就创建其为doctypeDecl类型标签

aToken = theAllocator->CreateTokenOfType(eToken_doctypeDecl,

eHTMLTag_doctypeDecl);

}

if (aToken) { //如果之前实例化了aToken

result = aToken->Consume(aChar, aScanner, mFlags); //调用aToken所对应类型的Consume解析方法

AddToken(aToken, result, &mTokenDeque, theAllocator); //将其添加至队列

}

if (result == kNotAComment) { //如果解析结果为非注释类型,即其为非法标签

result = ConsumeText(aToken, aScanner); //那么将其作为普通文本进行处理

}

return result;

}

//下面这个方法很简单,就是处理换行符的

/**

* This method is called just after a newlinehas been consumed.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

nsresult

nsHTMLTokenizer::ConsumeNewline(PRUnicharaChar,

CToken*&aToken,

nsScanner& aScanner)

{

// Get the newline character

aScanner.GetChar(aChar); //获取换行符,并创建相应的newline token

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

aToken = theAllocator->CreateTokenOfType(eToken_newline,eHTMLTag_newline);

nsresult result = NS_OK;

if (aToken) { //如果创建成功

result = aToken->Consume(aChar, aScanner, mFlags); //调用其Consume进行处理

AddToken(aToken, result, &mTokenDeque, theAllocator); //添加Token至队列

}

return result;

}

//最后一个方法,是用来对指令性标签进行处理的,当我们遇到了’<?’字符的时候就会去调用这个方法

/**

* This method is called just after a <? hasbeen consumed.

*

* @param aChar The last character read fromthe scanner.

* @param aToken The OUT parameter that holdsour resulting token.

* @param aScanner Our source of data

* @return Error result.

*/

nsresult

nsHTMLTokenizer::ConsumeProcessingInstruction(PRUnicharaChar,

CToken*& aToken,

nsScanner& aScanner)

{

// Get the "?"

aScanner.GetChar(aChar); //获取当前位置的’?’字符

//申请一个新的TokenAllocator

nsTokenAllocator* theAllocator = this->GetTokenAllocator();

aToken = theAllocator->CreateTokenOfType(eToken_instruction,

eHTMLTag_unknown);

nsresult result = NS_OK;

if (aToken) { //如果创建成功

result = aToken->Consume(aChar, aScanner, mFlags); //调用其对应的Consume进行处理

AddToken(aToken, result, &mTokenDeque, theAllocator); //添加至队列

}

return result;

}

后续:Mozilla Firefox Gecko内核源代码解析(3.nsScanner)

分享到:
评论

相关推荐

    Gecko浏览器内核

    Gecko:Netscape6开始采用的内核,后来的Mozilla FireFox(火狐浏览器) 也采用了该内核,Gecko的特点是代码完全公开,因此,其可开发程度很高,全世界的程序员都可以为其编写代码,增加功能。因为这是个开源内核,...

    Firefox Setup 41.0.2.exe

    Mozilla Firefox,中文俗称“火狐”(正式缩写为Fx或fx,非正式缩写为MF),是一个自由及开放源代码的网页浏览器,使用Gecko排版引擎,支持多种操作系统,如Windows、Mac OS X及GNU/Linux等。该浏览器提供了两种版本...

    Mozilla Firefox 4

    Mozilla Firefox是一个自由的,开放源码的浏览器,适用于Windows, Linux 和MacOS X平台,它体积小速度快,还有其它一些高级特征,主要特性有:标签式浏览,使上网冲浪更快;可以禁止弹出式窗口;自定制工具栏;扩展管理;更好...

    火狐浏览器for linux v52.0.2.zip

    火狐浏览器英文全称Mozilla Firefox,是一个开源网页浏览器,使用Gecko引擎(非ie内核),支持多种操作系统如Windows、Mac和linux。 火狐浏览器for linux v52.0.2更新日志: 全新的定制模式让自定义你的网络体验更...

    Mozilla Firefox_v50.1.0_x86

    Mozilla Firefox,通称Firefox,中文也通称火狐,是一个自由及开源的网页浏览器[14],由Mozilla基金会及其子公司Mozilla公司开发。Firefox支持Windows、macOS及Linux,其移动版支持Android及Firefox OS,这些版本的...

    火狐浏览器驱动geckodriver.exe.rar

    为了测试爬虫使用模拟火狐浏览器进行爬取网页内容,该文件包含了win和linux版本的geckodriver驱动,下载后直接把路径放到项目中即可,版本都是最新的geckodriver-v0.27.0,下载了半年才下载下来,想要的5积分拿走。

    selenium3.141+geckodriver0.26.0_firefox.zip for python

    用于python网站自动化测试的两个重要环境安装包selenium3.141+geckodriver0.26.0_firefox

    Gecko内核官方完整文件

    Gecko是Firefox浏览器应用内核,本文件为完整版,从github复制下来的,希望可以对有开发需求或学习需求的朋友们给予便利和帮助。

    firefox52+geckodriver0.11.1+firebug+firepath+selenium_ide五合一.rar

    firefox52+geckodriver0.11.1+firebug+firepath+selenium_ide五合一

    JAVA编写的Gecko内核浏览器源码

    用JAVA编写的多种浏览器Demo程序,集成火狐的Gecko内核、调用IE内核

    Selenium Firefox geckodriver-v0.19.1-win32 driver

    Selenium Firefox geckodriver-v0.19.1 版本的32位驱动程序。

    Mozilla Firefox 29

    Mozilla Firefox,中文名通常称为“火狐”或“火狐浏览器”,是一个开源网页浏览器,使用Gecko引擎(非ie内核),支持多种操作系统如Windows、Mac和linux。Firefox由Mozilla基金会与社区数百个志愿者开发。

    火狐Firefox浏览器驱动geckodriver最新版

    火狐Firefox浏览器驱动geckodriver最新版,,火狐Firefox浏览器驱动geckodriver最新版

    skybound.gecko.rar

    skybound.gecko.dll文件,skybound.gecko.dll文件 skybound.gecko.dll文件免费下载

    geckodriver.exe4个版本下载

    由于webdriver在不同的firefox版本中,需要的geckodriver.exe版本也不同,所以需要对照版本下载不同的geckodriver.exe,资源中包括了geckodriver.exe 15、16、19、23 4个版本的资源

    Firefox火狐浏览器官方50.0.1-win32版本exe安装包

    解压后可用,资源全名:Firefox Setup 50.0.1.exe

    Firefox-ERS-68.12.0-x32.msi

    Mozilla Firefox,中文俗称“火狐”(正式缩写为Fx或fx,非正式缩写为MF),是一个自由及开放源代码的网页浏览器,使用Gecko排版引擎,支持多种操作系统,如Windows、Mac OS X及GNU/Linux等。该浏览器提供了两种版本...

    geckodriver-0.26.0.tar.gz

    使用Selenium进行Web自动化测试时,浏览器如果使用的是Firefox(火狐) 那么在Selenium的版本选择和Firefox版本选择以及是否需要单独的WebDriver驱动上面。

    geckodriver-v0.15.0-macos.tar

    geckodriver是一原生态的第三方浏览器,对于selenium3.x版本都会使用geckodriver来驱动firefox,所以需要下载geckodriver.exe。放置在Path 环境变量可以访问到的地方。例如 C:\python34

Global site tag (gtag.js) - Google Analytics