. HTML 분석기

분석기는 가급적이면 많이 제공할수록 좋다. 편집기가 많은 분석기를 거느리고 있으면 그만큼 분석할 수 있는 문서의 종류가 많다는 뜻이며 편집기의 능력이 많아진다는 얘기와 같다. 다다익선인 것은 분명한 사실이지만 그렇다고 존재하는 모든 문서에 대한 분석기를 다 제공할 수는 없으므로 많이 쓰이는 문법순으로 분석기를 추가하도록 하자.

요즘은 이른바 인터넷 시대이므로 HTML 파일을 편집할 일이 많다. C++이나 자바, 파스칼 같은 프로그래밍 소스는 전용 편집기들이 제공되므로 독립된 편집기를 쓸 일이 그다지 많지 않지만 HTML은 아직도 편집기로 직접 편집하는 사람들이 많다. HTML 분석기를 만들어 보면서 분석기들이 CParse 부모 클래스의 기능을 어떻게 잘 활용하는지를 연구해보도록 하자. Parse.h CParseHtml 클래스를 다음과 같이 선언한다.

 

 

class CParseHtml : public CParse

{

public:

     CParseHtml();

     ~CParseHtml() {};

     TCHAR *GetInfo(int iIndex);

 

private:

     void ParseLine(CApiEdit &ae,int nLine);

     BOOL IsTag(CApiEdit &ae,int s, int e);

     BOOL IsAttr(CApiEdit &ae,int s, int e);

     BOOL IsPhpKeyword(CApiEdit &ae,int s, int e);

     BOOL IsPhpFunction(CApiEdit &ae,int s, int e);

     BOOL IsAspKeyword(CApiEdit &ae,int s, int e);

};

 

CParseCpp와 멤버 구성이 거의 유사하다. 키워드 대신 태그와 속성을 가진다는 것이 다르고 내부에 ASP, PHP 스크립트를 포함할 수 있다는 점에 있어서 C/C++보다는 훨씬 더 복잡한 문법이다. 그래서 CParseCpp 클래스에 비해서 훨씬 더 컨텍스트와 스타일값이 많이 정의되어 있고 분석 방법도 복잡하다. Parse.cpp에 구현 코드를 다음과 같이 작성한다.

 

enum {

     HTML_CON_NORMAL,

     HTML_CON_BRACKET,

     HTML_CON_COMMENT,

     HTML_CON_VALUE1,

     HTML_CON_VALUE2,

     HTML_CON_ASP,

     HTML_CON_ASPLINECOM,

     HTML_CON_ASPSTRING,

     HTML_CON_PHP,

     HTML_CON_PHPSTRING,

     HTML_CON_PHPLINECOM,

     HTML_CON_PHPBLOCKCOM

};

 

enum {

     HTML_STYLE_NORMAL,

     HTML_STYLE_TAG,

     HTML_STYLE_ATTR,

     HTML_STYLE_VALUE,

     HTML_STYLE_COMMENT,

     HTML_STYLE_ASP,

     HTML_STYLE_ASPKEY,

     HTML_STYLE_ASPSTRING,

     HTML_STYLE_ASPCOMMENT,

     HTML_STYLE_PHP,

     HTML_STYLE_PHPKEY,

     HTML_STYLE_PHPSTRING,

     HTML_STYLE_PHPCOMMENT,

     HTML_STYLE_PHPFUNCTION

};

 

CParseHtml::CParseHtml()

{

     lstrcpy(arStyle[0].name,"보통");

     arStyle[0].fore=(DWORD)-1;

     lstrcpy(arStyle[1].name,"태그");

     arStyle[1].fore=RGB(0,0,255);

     lstrcpy(arStyle[2].name,"속성");

     arStyle[2].fore=RGB(255,0,0);

     lstrcpy(arStyle[3].name,"속성값");

     arStyle[3].fore=RGB(0,128,0);

     lstrcpy(arStyle[4].name,"주석");

     arStyle[4].fore=RGB(0,0,0);

     arStyle[4].back=RGB(192,192,192);

     lstrcpy(arStyle[5].name,"ASP 스크립트");

     arStyle[5].fore=(DWORD)-1;

     arStyle[5].back=RGB(141,207,244);

     lstrcpy(arStyle[6].name,"ASP 키워드");

     arStyle[6].fore=(DWORD)RGB(0,0,255);

     arStyle[6].back=RGB(141,207,244);

     lstrcpy(arStyle[7].name,"ASP 문자열");

     arStyle[7].fore=RGB(0,128,0);

     arStyle[7].back=RGB(141,207,244);

     lstrcpy(arStyle[8].name,"ASP 주석");

     arStyle[8].fore=RGB(128,128,128);

     arStyle[8].back=RGB(141,207,244);

     lstrcpy(arStyle[9].name,"PHP 스크립트");

     arStyle[9].fore=(DWORD)-1;

     arStyle[9].back=RGB(255,249,157);

     lstrcpy(arStyle[10].name,"PHP 키워드");

     arStyle[10].fore=RGB(0,0,255);

     arStyle[10].back=RGB(255,249,157);

     lstrcpy(arStyle[11].name,"PHP 문자열");

     arStyle[11].fore=RGB(0,128,0);

     arStyle[11].back=RGB(255,249,157);

     lstrcpy(arStyle[12].name,"PHP 주석");

     arStyle[12].fore=RGB(128,128,128);

     arStyle[12].back=RGB(255,249,157);

     lstrcpy(arStyle[14].name,"PHP 함수");

     arStyle[13].fore=RGB(255,0,0);

     arStyle[13].back=RGB(255,249,157);

}

 

TCHAR *CParseHtml::GetInfo(int iIndex)

{

     switch (iIndex) {

     case 0:

          return (TCHAR *)2;

     case 1:

          return TEXT(" \t\r\n\"\’\\.,<>:;/()[]{}~!@#%^&*-+=?|$");

     case 2:

          return TEXT("");

     case 3:

          return TEXT("<!--");

     case 4:

          return TEXT("-->");

     case 5:

          return TEXT("");

     case 6:

          return TEXT("");

     }

     return 0;

}

 

void CParseHtml::ParseLine(CApiEdit &ae,int nLine)

{

     DWORD Context;

     int s,e,i;

     int nUnit=0;

     int idpos,idend;

 

     if (pInfo[nLine].pUnit[0].pos != -1)

          return;

 

     if (nLine == 0) {

          Context=HTML_CON_NORMAL;

     } else {

          Context=pInfo[nLine-1].Context;

     }

 

     s=ae.pLine[nLine].Start;

     e=ae.pLine[nLine].End;

     idpos=s;

 

     switch(Context) {

     case HTML_CON_NORMAL:

     case HTML_CON_BRACKET:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_NORMAL);

          break;

     case HTML_CON_COMMENT:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_COMMENT);

          break;

     case HTML_CON_VALUE1:

     case HTML_CON_VALUE2:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_VALUE);

          break;

     case HTML_CON_ASP:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_ASP);

          break;

     case HTML_CON_PHP:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_PHP);

          break;

     case HTML_CON_PHPSTRING:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_PHPSTRING);

          break;

     case HTML_CON_PHPLINECOM:

          if (ae.pLine[nLine].nLine != 0) {

              MakeParseInfo(nLine,nUnit,s,HTML_STYLE_PHPCOMMENT);

              goto EndParse;

          } else {

              MakeParseInfo(nLine,nUnit,s,HTML_STYLE_PHP);

              Context=HTML_CON_PHP;

              break;

          }

          break;

     case HTML_CON_ASPLINECOM:

          if (ae.pLine[nLine].nLine != 0) {

              MakeParseInfo(nLine,nUnit,s,HTML_STYLE_ASPCOMMENT);

              goto EndParse;

          } else {

              MakeParseInfo(nLine,nUnit,s,HTML_STYLE_ASP);

              Context=HTML_CON_ASP;

              break;

          }

          break;

     case HTML_CON_PHPBLOCKCOM:

          MakeParseInfo(nLine,nUnit,s,HTML_STYLE_COMMENT);

          break;

     }

 

     for (i=s;i<e;i++) {

          switch(Context) {

          case HTML_CON_NORMAL:

              if (ae.buf[i]==‘<‘ && ae.buf[i+1]==‘!’ && ae.buf[i+2]==‘-’ && ae.buf[i+3]==‘-’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_COMMENT);

                   i+=3;

                   Context=HTML_CON_COMMENT;

                   continue;

              }

              if (ae.buf[i]==‘<‘ && ae.buf[i+1]==‘%’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_ASP);

                   Context=HTML_CON_ASP;

                   continue;

              }

              if (ae.buf[i]==‘<‘ && ae.buf[i+1]==‘?’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_PHP);

                   Context=HTML_CON_PHP;

                   continue;

              }

              if (ae.buf[i]==‘<‘) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_TAG);

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_BRACKET;

                   idpos=i+1;

                   continue;

              }

              continue;

          case HTML_CON_BRACKET:

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_VALUE);

                   Context=HTML_CON_VALUE1;

                   continue;

              }

              if (ae.buf[i]==‘\’’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_VALUE);

                   Context=HTML_CON_VALUE2;

                   continue;

              }

              if (strchr(GetInfo(1),ae.buf[i]) || i==e-1) {

                   if (i==e-1 && !strchr(GetInfo(1),ae.buf[i])) {

                        idend=i;

                   } else {

                        idend=i-1;

                   }

                   if (idend-idpos+1 >= 1) {

                        if ((idpos > 0 && ae.buf[idpos-1] == ‘<‘) || (idpos > 1 && ae.buf[idpos-2]==‘<‘ && ae.buf[idpos-1]==‘/’)) {

                             if (IsTag(ae,idpos,idend)) {

                                 if (idpos > s && ae.buf[idpos-1] == ‘/’ )

                                      idpos--;

                                 MakeParseInfo(nLine,nUnit,idpos,HTML_STYLE_TAG);

                                 MakeParseInfo(nLine,nUnit,idend+1,HTML_STYLE_NORMAL);

                             }

                        } else

                        if (IsAttr(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,HTML_STYLE_ATTR);

                             MakeParseInfo(nLine,nUnit,idend+1,HTML_STYLE_NORMAL);

                        }

                   }

 

                   idpos=i+1;

              }

 

              if (ae.buf[i]==‘>‘) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_TAG);

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_NORMAL;

                   continue;

              }

              continue;

          case HTML_CON_COMMENT:

              if (ae.buf[i]==‘-’ && ae.buf[i+1]==‘-’ && ae.buf[i+2]==‘>‘) {

                   i+=2;

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_NORMAL;

                   idpos=i+1;

              }

              continue;

          case HTML_CON_VALUE1:

              if (ae.buf[i]==‘\\’) {

                   i++;

                   continue;

              }

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_BRACKET;

                   idpos=i+1;

              }

              continue;

          case HTML_CON_VALUE2:

              if (ae.buf[i]==‘\\’) {

                   i++;

                   continue;

              }

              if (ae.buf[i]==‘\’’) {

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_BRACKET;

                   idpos=i+1;

              }

              continue;

          case HTML_CON_ASP:

              if (ae.buf[i]==‘%’ && ae.buf[i+1]==‘>‘) {

                   i++;

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_NORMAL;

                   idpos=i+1;

              }

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_ASPSTRING);

                   Context=HTML_CON_ASPSTRING;

                   continue;

              }

              if (ae.buf[i]==‘\’’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_ASPCOMMENT);

                   Context=HTML_CON_ASPLINECOM;

                   goto EndParse;

              }

              if (strchr(GetInfo(1),ae.buf[i]) || i==e-1) {

                   if (i==e-1 && !strchr(GetInfo(1),ae.buf[i])) {

                        idend=i;

                   } else {

                        idend=i-1;

                   }

                   if (idend-idpos+1 >= 1) {

                        if (IsAspKeyword(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,HTML_STYLE_ASPKEY);

                             MakeParseInfo(nLine,nUnit,idend+1,HTML_STYLE_ASP);

                        }

                   }

 

                   idpos=i+1;

              }

              continue;

          case HTML_CON_ASPSTRING:

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_ASP);

                   Context=HTML_CON_ASP;

                   idpos=i+1;

              }

              continue;

          case HTML_CON_PHP:

              if (ae.buf[i]==‘?’ && ae.buf[i+1]==‘>‘) {

                   i++;

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_NORMAL);

                   Context=HTML_CON_NORMAL;

                   idpos=i+1;

                   continue;

              }

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_PHPSTRING);

                   Context=HTML_CON_PHPSTRING;

                   continue;

              }

              if (ae.buf[i]==‘/’ && ae.buf[i+1]==‘*’) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_PHPCOMMENT);

                   i++;

                   Context=HTML_CON_PHPBLOCKCOM;

                   continue;

              }

              if ((ae.buf[i]==‘/’ && ae.buf[i+1]==‘/’) || (ae.buf[i]==‘#’)) {

                   MakeParseInfo(nLine,nUnit,i,HTML_STYLE_PHPCOMMENT);

                   Context=HTML_CON_PHPLINECOM;

                   goto EndParse;

              }

              if (strchr(GetInfo(1),ae.buf[i]) || i==e-1) {

                   if (i==e-1 && !strchr(GetInfo(1),ae.buf[i])) {

                        idend=i;

                   } else {

                        idend=i-1;

                   }

                   if (idend-idpos+1 >= 1) {

                        if (IsPhpKeyword(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,HTML_STYLE_PHPKEY);

                             MakeParseInfo(nLine,nUnit,idend+1,HTML_STYLE_PHP);

                        } else if (IsPhpFunction(ae,idpos,idend)) {

                             MakeParseInfo(nLine,nUnit,idpos,HTML_STYLE_PHPFUNCTION);

                             MakeParseInfo(nLine,nUnit,idend+1,HTML_STYLE_PHP);

                        }

                   }

 

                   idpos=i+1;

              }

              continue;

          case HTML_CON_PHPSTRING:

              if (ae.buf[i]==‘\"‘) {

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_PHP);

                   Context=HTML_CON_PHP;

                   idpos=i+1;

              }

              continue;

          case HTML_CON_PHPBLOCKCOM:

              if (ae.buf[i]==‘*’ && ae.buf[i+1]==‘/’) {

                   i++;

                   MakeParseInfo(nLine,nUnit,i+1,HTML_STYLE_PHP);

                   Context=HTML_CON_PHP;

                   idpos=i+1;

              }

              continue;

          }

     }

 

EndParse:

     pInfo[nLine].Context=Context;

}

 

BOOL CParseHtml::IsTag(CApiEdit &ae,int s, int e)

{

     static TCHAR *tag="a abbr above acronym address applet array "

          "area b base basefont bdo bgsound big blink blockquote "

          "body box br button caption center cite code col "

          "colgroup comment dd del dfn dir div dl dt em embed "

          "fieldset fig font form frame frameset h1 h2 h3 h4 "

          "h5 h6 head hr html i iframe ilayer img input ins "

          "isindex kbd label layer legend li link listring map "

          "marquee menu meta multicol nextid nobr noframes nolayer "

          "note noscript object ol option optgroup p param pre "

          "q quote range root s samp script select small sound "

          "spacer span sqrt strike strong style sub sup table "

          "tbody td text textarea tfoot th thead title tr tt "

          "u ul var wbr xmp ";

 

     return IsStringExist(tag,ae.buf+s,e-s+1,FALSE);

}

 

BOOL CParseHtml::IsAttr(CApiEdit &ae,int s, int e)

{

     static TCHAR *attr="abbr accetp-charset accept accesskey action "

          "align alink alt archive axis background behavior below "

          "bgcolor border cellpadding cellspacing char charoff "

          "charset checked cite class classid clear code codebase "

          "codetype color cols colspan compact content coords data "

          "datetime dir enctype face for frame frameborder "

          "framespacing headers height hidden href hreflang hspace "

          "http-equiv id ismap label lang language link loop "

          "longdesc mailto marginheight marginwidth maxlength media "

          "method multiple name nohref noresize noshade object "

          "onblur onchange onfocus onkeydown onkeypress onkeyup "

          "onload onreset onselect onsubmit onunload onclick "

          "ondblclick onmousemdown onmousemove onmouseout onmouseover "

          "onmouseup profile prompt readonly rel rev rows rowspan "

          "rules scheme scope scrolling selected shape size span "

          "src standby start style summary tabindex target text "

          "tile topmargin type url usemap valign value valuetype "

          "version vlink vspace width ";

 

     return IsStringExist(attr,ae.buf+s,e-s+1,FALSE);

}

 

BOOL CParseHtml::IsPhpKeyword(CApiEdit &ae,int s, int e)

{

     static TCHAR *pkey="array bool break case char class "

          "continue default do double else elseif endfor endif "

          "endswitch endwhile extends false float for foreach "

          "function global if include int integer long mixed "

          "new object old_function php real require return static "

          "string switch true var void while ";

 

     return IsStringExist(pkey,ae.buf+s,e-s+1,TRUE);

}

 

BOOL CParseHtml::IsPhpFunction(CApiEdit &ae,int s, int e)

{

     static TCHAR *pfun="abs acos addcslashes addslashes "

          "apache_lookup_uri apache_note asin atan atan2 base_convert "

          "bin2hex bindex ceil chdir checkdate chop chr "

          "chunk_split closedir convert_cyr_string cos count_chars "

          "crc32 crypt date decbin dechex decoct deg2rad dir "

          "echo exp explode floor flush get_html_translation_table "

          "get_meta_tags getallheaders getcwd getdate getrandmax "

          "gettimeofday gmdate gmmktime gmstrftime hebrev hebrevc "

          "hexdec htmlentities htmlspecialchars implode join lcg "

          "levenshtein localtime log log10 ltrim max md5 "

          "metaphone microtime min mktime ml2br mt_getrandmax "

          "mt_rand mt_srand mysql_affected_rows mysql_change_user "

          "mysql_close mysql_connect mysql_create_db mysql_data_seek "

          "mysql_db_name mysql_db_query mysql_drop_db mysql_errno "

          "mysql_error mysql_fetch_array mysql_fetch_field "

          "mysql_fetch_lengths mysql_fetch_object mysql_fetch_row "

          "mysql_field_flags mysql_field_len mysql_field_name "

          "mysql_field_seek mysql_field_table mysql_free_result "

          "mysql_insert_id mysql_list_dbs mysql_list_fields "

          "mysql_list_tables mysql_num_fields mysql_num_rows "

          "mysql_pconnect mysql_query mysql_result mysql_select_db "

          "mysql_tablename mysql_type number_fromat octdec opendir "

          "ord parse_str pi pow print printf "

          "quoted_printable_decode quotemeta rad2deg rand rawurldecode "

          "rawurlencode readdir rewinddir round rtrim setlocale "

          "similar_text sin soundex sprintf sqry srand sscanf "

          "str_pad str_repeat str_replace strcasecmp strchr strcmp "

          "strcspn strftime strip_tags stripcslashes stripslatshes "

          "stristr strlen strnatcasecmp strnatcmp strncmp strpos "

          "strrchr strrev strrpos strspn strstr strtolower "

          "strtotime strtoupper strtr strtrok substr substr_count "

          "substr_replace tan time trim ucfirst ucwords virtual "

          "wordwrap ";

 

     return IsStringExist(pfun,ae.buf+s,e-s+1,TRUE);

}

 

BOOL CParseHtml::IsAspKeyword(CApiEdit &ae,int s, int e)

{

     static TCHAR *akey="and call case delete dim dowhile else "

          "elseif end false for foreach from function if in "

          "insert int into isdate isnull isnumeric isstring key "

          "left len like loop mid next or right select set "

          "split sub then to true update values wend where "

          "while with ";

 

     return IsStringExist(akey,ae.buf+s,e-s+1,TRUE);

}

 

HTML 분석기의 특징은 HTML 문법의 특징으로부터 유래된다. HTML은 앞에서 먼저 만들어 보았던 C/C++ 문서에 비해 다음과 같은 뚜렷한 특징을 가진다.

 

HTML 문서 내부에 ASP, PHP 같은 별도의 스크립트 언어가 포함된다. 이 두 개의 스크립트 외에도 JSP 같은 서버 스크립트도 있고 자바 스크립트, VB 스크립트 같은 클라이언트 스크립트도 있다. 뿐만 아니라 ASP, PHP 스크립트는 또한 내부에 SQL 스크립트를 포함할 수 있다. 이런 식으로 문법이 문법을 중첩하는 복잡한 구조를 가지는 문서가 바로 HTML이다. 각 스크립트는 시작과 끝을 나타내는 고유의 기호 블록으로 정의되며 스크립트 블록 내에서는 자신만의 고유한 문법을 따로 가진다.

이런 포함 문법들도 같이 분석해야 하는데 완벽하기 어려우므로 각 포함 문법에 대해 분석기를 따로 제공하는 편집기들도 있다. ApiEdit는 다른 스크립트는 포기하더라도 ASP PHP에 대해서만은 같이 분석하기로 하되 스크립트 블록에서는 별도의 컨텍스트를 할당하여 스크립트끼리 충돌하지 않도록 하였다.

C/C++에서 키워드, 숫자 등은 노말 상태에서만 올 수 있으며 주석이나 문자열 상수에 포함된 키워드는 키워드로 인식되지 않는다. HTML에서는 태그, 속성, 값 등의 스타일이 브라킷 컨텍스트에서만 올 수 있으며 노말 상태에서는 오지 못한다. table, body 같은 태그나 width, href 같은 속성은 < >괄호안에서만 의미가 있다. 반대로 주석은 노말 상태에서만 올 수 있으며 브라킷 컨텍스트에서는 올 수 없다.

HTML에는 한 줄 주석이 없으며 <!--로 시작해서 -->로 끝나는 블록 주석만 있다. 하지만 ASP PHP 포함 문법들은 고유의 한 줄 주석을 정의한다.

속성의 값을 표현하는 형식이 두 가지다. 문자열 표현에 "가 사용될 수 있고 문자열 안에 문자열이 올 수 있다는 것이 큰 특징이다. 예를 들어 "print(str)"이나 "Lets go" 같은 표현이 가능하다. 하지만 괄호의 짝은 반드시 맞추어야 한다. 겹따옴표로 열고 홑따옴표 닫거나 해서는 안된다. HTML분석기는 겹따옴표로 열린 값과 홑따옴표로 열린 값을 구분하기 위해 VALUE1, VALUE2 두 개의 컨텍스트를 정의하고 있다.

 

CParseHtml ASP, PHP 스크립트를 같이 처리하기 때문에 무려 14개나 되는 스타일을 열거형으로 정의하고 있다. 각 스타일의 의미는 열거형 이름으로 쉽게 유추 가능할 것이다. GetInfo 함수는 HTML 문법의 ID와 구분자를 정의하는데 C 언어에 비해 $ 문자가 구분자에 추가되었다는 점이 조금 다르다.

CParseCpp와 마찬가지로 구문 분석의 핵심은 ParseLine 함수이다. 컨텍스트와 스타일 구조를 먼저 보고 이 함수의 동작을 살펴 보면 HTML 분석기가 어떻게 동작하는지 볼 수 있다. 길이는 길지만 논리는 CParseCpp와 거의 유사하다. C/C++ 분석기와 마찬가지로 노말 컨텍스트에서 분석을 시작하며 컨텍스트 흐름도는 다음과 같다.

이 흐름도를 그대로 코드로 옮겨놓은 것이 ParseLine 함수라고 생각하면 된다. 분석중에 문자열 점검을 위해 호출되는 IsTag, IsAttr 함수는 CParseCpp::IsKeyword 함수와 완전히 같은 방식으로 태그와 속성을 조사한다. 문법의 특성이 조금 다를 뿐이지 CParseCpp와 코드 수준에서는 거의 차이점이 없으므로 더 이상 세부 분석은 하지 않기로 한다.