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
 *
 * @param aParseMode 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
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值