web-dev-qa-db-ja.com

パーサー(HTMLなど)はどのように機能しますか?

議論のために、HTMLパーサーを想定しましょう。

最初にすべてをトークン化してから、それを解析することを読みました。

トークン化とはどういう意味ですか?

パーサーはすべての文字をそれぞれ読み取り、構造を格納するための多次元配列を構築しますか?

たとえば、<を読み取り、要素のキャプチャを開始し、終了する>(属性の外)に到達すると、どこかの配列スタックにプッシュされますか?

知るために興味があります(興味があります)。

HTML Purifier のようなソースを読んだ場合、HTMLがどのように解析されるかについての良いアイデアが得られますか?

38
alex

まず第一に、HTMLの解析は特に醜いことに注意する必要があります。HTMLは標準化される前は広く(そして発散的に)使用されていました。これは、一部の構成が許可されないことを指定する標準など、あらゆる種類の醜さにつながりますが、とにかくそれらの構成に必要な動作を指定します。

直接の質問に答える:トークン化は、英語を取得して単語に分割することとほぼ同じです。英語では、ほとんどの単語は文字の連続したストリームであり、アポストロフィ、ハイフンなどが含まれる可能性があります。ほとんどの単語はスペースで囲まれていますが、ピリオド、疑問符、感嘆符なども単語の終わりを示します。同様に、HTML(またはその他)の場合、この言語でトークン(Word)を構成できるものに関するいくつかのルールを指定します。入力をトークンに分割するコードは、通常、レクサーと呼ばれます。

少なくとも通常の場合、解析を開始する前に、notすべての入力をトークンに分割します。むしろ、パーサーはレクサーを呼び出して、必要なときに次のトークンを取得します。呼び出されると、レクサーは1つのトークンを見つけるのに十分な入力を調べ、それをパーサーに配信します。次にパーサーがさらに入力を必要とするまで、入力はトークン化されません。

一般的に、パーサーの動作については正しいですが、(少なくとも一般的なパーサーでは)ステートメントの解析中にスタックを使用しますが、ステートメントを表すために構築されるのは通常、ツリーです(および多次元配列ではなく、抽象構文木(別名AST)。

HTMLの解析の複雑さに基づいて、最初に他のいくつかを読み終えるまで、HTMLのパーサーを検討することを予約します。周りを見回すと、数式など、導入部としておそらくより適しているもの(より小さく、より単純で、理解しやすいなど)のパーサー/レクサーをかなりの数見つけることができるはずです。

30
Jerry Coffin

トークン化は、たとえば、次のhtmlコードがある場合、いくつかのステップで構成できます。

<html>
    <head>
        <title>My HTML Page</title>
    </head>
    <body>
        <p style="special">
            This paragraph has special style
        </p>
        <p>
            This paragraph is not special
        </p>
    </body>
</html>

トークナイザーは、その文字列を重要なトークンのフラットリストに変換する場合があります。 空白を破棄する (訂正してくれたSasQに感謝します):

["<", "html", ">", 
     "<", "head", ">", 
         "<", "title", ">", "My HTML Page", "</", "title", ">",
     "</", "head", ">",
     "<", "body", ">",
         "<", "p", "style", "=", "\"", "special", "\"", ">",
            "This paragraph has special style",
        "</", "p", ">",
        "<", "p", ">",
            "This paragraph is not special",
        "</", "p", ">",
    "</", "body", ">",
"</", "html", ">"
]

次の架空のHTMLパーサーが行う可能性のあるように、トークンのリストをさらに高レベルのトークンのリストに変換するためのトークン化パスが複数ある場合があります(これはまだフラットリストです)。

[("<html>", {}), 
     ("<head>", {}), 
         ("<title>", {}), "My HTML Page", "</title>",
     "</head>",
     ("<body>", {}),
        ("<p>", {"style": "special"}),
            "This paragraph has special style",
        "</p>",
        ("<p>", {}),
            "This paragraph is not special",
        "</p>",
    "</body>",
"</html>"
]

次に、パーサーはそのトークンのリストを変換して、プログラムによるアクセス/操作に便利な方法でソーステキストを表すツリーまたはグラフを形成します。

("<html>", {}, [
    ("<head>", {}, [
        ("<title>", {}, ["My HTML Page"]),
    ]), 
    ("<body>", {}, [
        ("<p>", {"style": "special"}, ["This paragraph has special style"]),
        ("<p>", {}, ["This paragraph is not special"]),
    ]),
])

この時点で、解析は完了です。そして、ツリーを解釈したり、変更したりするのはユーザー次第です。

58
Lie Ryan

HTML5の解析 に関するW3Cのメモをお見逃しなく。

スキャン/字句解析の興味深い紹介については、Webでテーブル駆動型スキャナーの効率的な生成を検索してください。これは、スキャンが最終的にオートマトン理論によってどのように駆動されるかを示しています。正規表現のコレクションは単一の [〜#〜] nfa [〜#〜] に変換されます。次に、NFAは [〜#〜] dfa [〜#〜] に変換され、状態遷移が決定性になります。次に、DFAを遷移表に変換する方法について説明します。

重要なポイント:スキャナーは正規表現理論を使用しますが、既存の正規表現ライブラリを使用しない可能性があります。パフォーマンスを向上させるために、状態遷移は巨大なケースステートメントまたは遷移テーブルにコード化されます。

スキャナーは、正しい単語(トークン)が使用されていることを保証します。パーサーは、単語が正しい組み合わせと順序で使用されることを保証します。スキャナーは正規表現とオートマトン理論を使用します。パーサーは文法理論、特に 文脈自由文法 を使用します。

いくつかの解析リソース:

9
Corbin March

HTMLおよびXML構文(およびSGMLに基づくその他の構文)は解析が非常に難しく、regularではないため、字句解析シナリオにうまく適合しません。構文解析理論では、正規文法は再帰がないもの、つまり、自己相似、ネストされたパターン、または互いに一致する必要のある括弧のようなラッパーです。しかし、HTML/XML/SGMLベースの言語doesにはネストされたパターンがあります:タグはネストされる可能性があります。入れ子パターンの構文は、チョムスキーの分類のレベルが高く、文脈自由または文脈依存ですらあります。

しかし、レクサーについての質問に戻りましょう。
各構文は、2種類の記号で構成されています。非終端記号(他の構文規則に巻き戻される記号)と終端記号(「原子」である記号) "-それらは構文ツリーの葉であり、他の何にも巻き戻されません)。多くの場合、終端記号は単なるトークンです。トークンはレクサーから1つずつポンプされ、対応する終端記号と照合されます。

これらの終端記号(トークン)は、多くの場合、認識しやすい正規構文を持っています(そのため、正規文法に特化した、非正規文法のより一般的なアプローチを使用するよりも迅速に実行できるレクサーに因数分解されます)。 )。

したがって、HTML/XML/SGMLのような言語のレクサーを作成するには、レクサーで簡単に処理できるように、十分にアトミックで規則的な構文の部分を見つける必要があります。そして、ここで問題が発生します。これは、最初はどの部分がこれらであるかが明らかではないためです。私は長い間この問題に苦しんでいました。

しかしLie Ryan上記はこれらの部分を認識するのに非常に良い仕事をしました。そのための彼のためのブラボー!トークンの種類は次のとおりです。

  • TagOpener:<語彙素、タグの開始に使用されます。
  • TagCloser:>語彙素、タグの終了に使用されます。
  • ClosingTagMarker:/タグを閉じる際に使用される語彙素。
  • 名前:文字で始まる英数字シーケンス。タグ名と属性名に使用されます。
  • 値:さまざまな異なる文字、スペースなどを含めることができるテキスト。属性の値に使用されます。
  • 等しい:=語彙素、属性名をその値から分離するために使用されます。
  • 引用:'語彙素、属性値を囲むために使用されます。
  • DoubleQuote:"語彙素、属性値を囲むために使用されます。
  • PlainText:<文字を直接含まず、上記のタイプでカバーされていないテキスト。

&nbsp;&amp;など、エンティティ参照用のトークンをいくつか持つこともできます。多分:

  • EntityReference:&とそれに続くいくつかの英数字で構成され、;で終わる語彙素。

属性値に1つのトークンではなく、'"に別々のトークンを使用したのはなぜですか?通常の構文では、これらの文字のどれがシーケンスを終了する必要があるかを認識できなかったため、シーケンスを開始した文字によって異なります(終了文字は開始文字と一致する必要があります)。この「括弧で囲む」ことは、非正規の構文と見なされます。だから私はそれをより高いレベルに昇格させます-パーサーに。これらのトークン(開始と終了)を一緒に一致させる(または、スペースを含まない単純な属性値の場合はまったく一致させない)のが彼の仕事です。

Afterthought:残念ながら、これらのトークンの一部は、他のマークアップ内でのみ発生する可能性があります。したがって、lexical contextsを使用する必要があります。これは、結局のところ、特定のトークンを認識するステートマシンを制御する別のステートマシンです。そのため、SGMLのような言語は字句解析のスキーマにうまく適合しないと言いました。

7
SasQ

HTML5パーサーの仕組みは次のとおりです。

This is how HTML 5 Parser works

2
Vivek Kumar