web-dev-qa-db-ja.com

正規表現を使用してHTML / XMLを解析できない理由:素人の言葉での正式な説明

SOには、正規表現を使用した(X)HTMLまたはXMLの解析に関する質問なしでパスする日はありません。

このタスクの正規表現の非実行可能性を示す例 または概念を表す 式のコレクション を見つけるのは比較的簡単ですが、まだ見つかりませんでしたon SO aformalこれが素人の言葉でできない理由の説明。

このサイトでこれまでに見つけた正式な説明は、おそらく非常に正確ですが、独学のプログラマにとっては非常に不可解です。

ここでの欠点は、HTMLがChomsky Type 2文法(コンテキストフリー文法)であり、RegExがChomsky Type 3文法(正規表現)であることです

または:

正規表現は正規言語にのみ一致しますが、HTMLはコンテキストのない言語です。

または:

有限オートマトン(正規表現の基礎となるデータ構造)には、その状態以外のメモリはありません。また、任意の深さのネストがある場合は、任意の大きなオートマトンが必要です。これは、有限オートマトンの概念と衝突します。

または:

通常の言語のポンピング補題は、それができない理由です。

[公平に言うと、上記の説明の大部分はウィキペディアのページにリンクしていますが、これらは回答自体よりも理解しやすいものではありません]。

だから私の質問は次のとおりです:(X)HTML/XMLの解析に正規表現を使用できない理由についての上記の正式な説明について、誰かが素人の言葉で翻訳を提供してもらえますか?

EDIT:最初の答えを読んだ後、私は明確にする必要があると思った:私は簡潔にexplainsである「翻訳」を探している=翻訳しようとする概念:答えの最後に、読者は「規則的な言語」と「文脈自由文法」が何を意味するかについての大まかな考えを持っている必要があります。

107
mac

これに集中してください:

有限オートマトン(正規表現の基礎となるデータ構造)には、その状態以外のメモリはありません。また、任意の深さのネストがある場合は、任意の大きなオートマトンが必要です。これは、有限オートマトンの概念と衝突します。

正規表現のdefinitionは、文字列がパターンに一致するかどうかのテストを有限オートマトン(各パターンに1つの異なるオートマトン)で実行できるという事実と同等です。有限オートマトンにはメモリがありません-スタック、ヒープ、落書きする無限テープはありません。限られた数の内部状態のみがあり、それぞれがテスト対象の文字列から入力単位を読み取り、それを使用して次に移動する状態を決定できます。特別な場合として、「はい、一致した」と「いいえ、一致しなかった」という2つの終了状態があります。

一方、HTMLには、任意の深さにネストできる構造があります。ファイルが有効なHTMLかどうかを判断するには、すべての終了タグが以前の開始タグと一致することを確認する必要があります。それを理解するには、どの要素が閉じられているかを知る必要があります。あなたが見た開始タグを「記憶」する手段がなければ、チャンスはありません。

ただし、ほとんどの「正規表現」ライブラリでは、正規表現の厳密な定義以上のものが実際に許可されていることに注意してください。後方参照と一致できる場合は、通常の言語を超えています。したがって、HTMLで正規表現ライブラリを使用してはならない理由は、HTMLが通常ではないという単純な事実よりも少し複雑です。

103
Steve Jessop

HTMLが通常の言語を表していないという事実は、ニシンです。正規表現と正規言語類似の音、しかしそうではありません-それらは同じOriginを共有しますが、学術的な「正規言語」とエンジンの現在の一致する力の間にはかなりの距離があります。実際、ほとんどすべての最新の正規表現エンジンは非正規機能をサポートしています-簡単な例は(.*)\1。後方参照を使用して、繰り返される文字シーケンスに一致します。たとえば、123123、またはbonbon。再帰的/平衡構造のマッチングは、これらをさらに楽しくします。

ウィキペディアはこれを Larry Wall による引用文で引用しています。

「正規表現」[...]は、実際の正規表現にわずかに関連しています。それでも、この用語はパターンマッチングエンジンの機能とともに成長したため、ここでは言語の必要性と戦うつもりはありません。しかし、私は一般的にそれらを「正規表現」(またはアングロサクソンのムードにあるときは「正規表現」)と呼びます。

「正規表現は正規言語にのみ一致します」は、ご覧のとおり、一般的に述べられている誤statedにすぎません。

それでは、なぜそうではないのですか?

HTMLを正規表現と一致させない正当な理由は、「できるからといって、そうすべきではない」ということです。可能かもしれませんが、ジョブのための単なるより良いツールがあります。考慮:

  • 有効なHTMLは、想像以上に難しい/複雑です。
  • 「有効な」HTMLには多くの種類があります。たとえば、HTMLで有効なものは、XHTMLでは無効です。
  • インターネット上で見られるフリーフォームHTMLの多くはとにかく無効です。 HTMLライブラリもこれらをうまく処理し、これらの一般的なケースの多くでテストされました。
  • 非常に多くの場合、データを全体として解析せずにデータの一部を一致させることは不可能です。たとえば、すべてのタイトルを探して、コメントまたは文字列リテラル内で一致する場合があります。 <h1>.*?</h1>は、メインタイトルを見つけるための大胆な試みかもしれませんが、次のことを見つけるかもしれません。

    <!-- <h1>not the title!</h1> -->
    

    あるいは:

    <script>
    var s = "Certainly <h1>not the title!</h1>";
    </script>
    

最後の点が最も重要です:

  • 専用のHTMLパーサーを使用することは、思いつく正規表現よりも優れています。多くの場合、XPathを使用すると、必要なデータをより表現力豊かに検索できます。また、 HTMLパーサーの使用は、ほとんどの人が理解するよりもはるかに簡単です。

主題の概要、およびRegexとHTMLを組み合わせることが適切である場合の重要なコメントは、Jeff Atwoodのブログで見つけることができます: Parsing Html The Cthulhu Way

正規表現を使用してHTMLを解析する方がよいのはいつですか?

ほとんどの場合、ライブラリが提供できるDOM構造でXPathを使用することをお勧めします。それでも、一般的な意見に反して、パーサーライブラリではなく正規表現を使用することを強くお勧めする場合がいくつかあります。

これらの条件のいくつかを考えると:

  • HTMLファイルの1回限りの更新が必要で、構造が一貫していることがわかっている場合。
  • HTMLの非常に小さなスニペットがある場合。
  • HTMLファイルではなく、同様のテンプレートエンジンを扱う場合(その場合、パーサーを見つけるのは非常に困難です)。
  • HTMLの一部を変更したいが、すべてではない/-私の知る限り、パーサーはこのリクエストに答えることができません。ドキュメント全体を解析し、全体を保存しますドキュメント、変更したくない部分を変更します。
53
Kobi

HTMLは<tags><inside><tags and="<things><that><look></like></tags>"></inside></each></other>の無制限のネストを持つことができ、正規表現は、それがどのような結果になり、どのような結果になったかの履歴を追跡できないため、実際に対処できません。

難易度を示す簡単な構造:

<body><div id="foo">Hi there!  <div id="bar">Bye!</div></div></body>

一般化された正規表現ベースの抽出ルーチンの99.9%は、ID divを持つfoo内のすべてを、終了からそのdivの終了タグを伝えることができないため、正しく提供できません。 bar divのタグ。それは彼らが「大丈夫、私は今2つのdivの2番目に降りてきたので、私が見る次のdivを閉じると1つが戻ってきて、その後の1つが最初の閉じるタグです」と言う方法がないからです。プログラマーは通常、特定の状況に合わせて特殊なケースの正規表現を考案することで対応しますが、foo内にさらにタグが導入されるとすぐに壊れ、時間とフラストレーションの途方もないコストで解かれなければなりません。これが、人々がすべてに夢中になる理由です。

18

通常言語とは、有限状態マシンで照合できる言語です。

(有限状態マシン、プッシュダウンマシン、およびチューリングマシンについては、基本的に4年制大学CSコースのカリキュラムです。)

文字列 "hi"を認識する次のマシンを検討してください。

(Start) --Read h-->(A)--Read i-->(Succeed)
  \                  \
   \                  -- read any other value-->(Fail) 
    -- read any other value-->(Fail)

これは、通常の言語を認識する簡単なマシンです。括弧内の各式は状態であり、各矢印は遷移です。このようなマシンを構築すると、入力文字列を正規言語、つまり正規表現に対してテストできます。

HTMLでは、現在の状態だけでなく、タグのネストと一致させるために、以前に見たものの履歴が必要です。スタックをマシンに追加することでこれを実現できますが、スタックは「通常」ではなくなります。これはプッシュダウンマシンと呼ばれ、文法を認識します。

8
Sean McMillan

正規表現とは、有限の(通常はかなり少ない)離散状態を持つマシンです。

XML、C、または言語要素の任意のネストを持つ他の言語を解析するには、自分がどれだけ深いかを覚えておく必要があります。つまり、ブレース/ブラケット/タグをカウントできる必要があります。

有限のメモリではカウントできません。状態よりも多くのブレースレベルがあるかもしれません!ネストレベルの数を制限する言語のサブセットを解析できるかもしれませんが、非常に面倒です。

6
n.m.

文法とは、言葉がどこへ行くことができるかを正式に定義したものです。たとえば、形容詞は名詞_in English grammar_に先行しますが、名詞_en la gramática española_に続きます。コンテキストフリーとは、すべてのコンテキストでグラマーが普遍的に使用されることを意味します。コンテキスト依存とは、特定のコンテキストに追加のルールがあることを意味します。

たとえば、C#では、usingは、ファイルの先頭の_using System;_でusing (var sw = new StringWriter (...))とは異なるものを意味します。より適切な例は、コード内の次のコードです。

_void Start ()
{
    string myCode = @"
    void Start()
    {
       Console.WriteLine (""x"");
    }
    ";
}
_
6
agent-j

正規表現を使用してXMLとHTMLを解析しないもう1つの実用的な理由があります。これは、コンピューターサイエンス理論とはまったく関係ありません。正規表現は恐ろしく複雑であるか、間違っています。

たとえば、一致する正規表現を非常にうまく記述しています

<price>10.65</price>

ただし、コードが正しい場合は、次のようにします。

  • 開始タグと終了タグの両方で要素名の後に空白を許可する必要があります

  • ドキュメントがネームスペースにある場合、ネームスペースプレフィックスの使用を許可する必要があります

  • おそらく、開始タグに表示される不明な属性を許可および無視する必要があります(特定の語彙のセマンティクスに応じて)

  • 10進値の前後に空白を許可する必要がある場合があります(これも、特定のXMLボキャブラリーの詳細なルールによって異なります)。

  • 要素のように見えるものと一致するべきではありませんが、実際にはコメントまたはCDATAセクションにあります(悪意のあるデータがパーサーをだまそうとする可能性がある場合、これは特に重要になります)。

  • 入力が無効な場合、診断を提供する必要があります。

もちろん、これのいくつかはあなたが適用している品質基準に依存します。 StackOverflowには、特定の方法でXMLを生成する必要がある多くの問題があります(たとえば、タグに空白が含まれない)。特定の方法でXMLを記述する必要があるアプリケーションによって読み取られるためです。コードに何らかの寿命がある場合、コードをテストしている1つのサンプル入力ドキュメントだけでなく、XML標準で許可されている方法で記述された着信XMLを処理できることが重要です。

4
Michael Kay

純粋に理論的な意味では、正規表現がXMLを解析することは不可能です。これらは、以前の状態のメモリを許可しないように定義されているため、任意のタグの正しい一致を妨げます。また、ネストは正規表現に組み込む必要があるため、ネストの任意の深さまで侵入することはできません。

ただし、最新の正規表現パーサーは、正確な定義を順守するのではなく、開発者にとっての有用性のために構築されています。そのため、以前の状態の知識を利用する後方参照や再帰などがあります。これらを使用すると、XMLを探索、検証、または解析できる正規表現を簡単に作成できます。

たとえば、

(?:
    <!\-\-[\S\s]*?\-\->
    |
    <([\w\-\.]+)[^>]*?
    (?:
        \/>
        |
        >
        (?:
            [^<]
            |
            (?R)
        )*
        <\/\1>
    )
)

これにより、次の適切に形成されたXMLタグまたはコメントが検出され、コンテンツ全体が適切に形成された場合にのみ検出されます。 (この式は、PCREにほぼ近いBoost C++の正規表現ライブラリを使用するNotepad ++を使用してテストされています。)

仕組みは次のとおりです。

  1. 最初のチャンクはコメントに一致します。これが最初に来て、それ以外の場合はハングアップを引き起こす可能性のあるコメントアウトされたコードを処理する必要があります。
  2. それが一致しない場合、タグの先頭を探します。括弧を使用して名前をキャプチャすることに注意してください。
  3. このタグは/>で終わるため、タグが完成します。または、>で終わります。この場合、タグの内容を調べて続行します。
  4. <に到達するまで解析を続け、その時点で式の先頭に再帰し、コメントまたは新しいタグのいずれかを処理できるようにします。
  5. テキストの終わりまたは解析できない<に到達するまで、ループを継続します。もちろん、一致しないと、プロセスが最初からやり直されます。それ以外の場合、<はおそらくこの反復の終了タグの始まりです。終了タグ<\/\1>内の後方参照を使用すると、現在の反復(深さ)の開始タグと一致します。キャプチャグループは1つしかないため、この一致は簡単です。これにより、使用するタグの名前に依存しなくなりますが、必要に応じて特定のタグのみをキャプチャするようにキャプチャグループを変更できます。
  6. この時点で、現在の再帰から次のレベルまでキックアウトするか、マッチで終了します。

この例では、<または>を単に否定する文字グループを使用して、またはコメントの場合は[\S\s]を使用して、空白の処理または関連コンテンツの識別に関する問題を解決します。単一行モードであっても、-->に達するまで続きます。したがって、意味のある何かに到達するまで、すべてを有効なものとして扱います。

ほとんどの場合、このような正規表現は特に有用ではありません。 XMLが適切に形成されていることを検証しますが、実際に行うのはそれだけです。プロパティは考慮されません(ただし、簡単に追加できます)。タグ名の定義だけでなく、このような現実の問題も除外されるため、これは簡単です。実際に使用するために適合させると、より多くの獣になります。一般的に、真のXMLパーサーははるかに優れています。これはおそらく、再帰の仕組みを教えるのに最適です。

簡単に言えば、実際の作業にはXMLパーサーを使用し、正規表現をいじりたい場合はこれを使用します。

2
bükWyrm

正規表現でXML/HTMLを解析せず、適切なXML/HTMLパーサーと強力な xpath クエリを使用します。

理論:

コンパイル理論によると、XML/HTMLは 有限状態マシン に基づく正規表現を使用して解析できません。 XML/HTMLの階層構造のため、 プッシュダウンオートマトン を使用し、 [〜#〜] lalr [〜#〜] のようなツールを使用して [〜#〜] yacc [〜#〜]

シェル のrealLife©®™日常ツール

次のいずれかを使用できます。

xmllintlibxml2、xpath1でデフォルトでインストールされることがよくあります( my wrapper で区切られた改行で出力を確認してください)

xmlstarlet 編集、選択、変換が可能...デフォルトではインストールされない、xpath1

xpath PerlのモジュールXML :: XPath、xpath1を介してインストール

xidel xpath3

saxon-lint 自分のプロジェクト、@ Michael KayのSaxon-HEのラッパーJava library、xpath3

または、高レベルの言語と適切なライブラリを使用できます。

pythonlxmlfrom lxml import etree

PerlXML::LibXMLXML::XPathXML::Twig::XPath 、- HTML::TreeBuilder::XPath

Rubynokogiriこの例を確認

phpDOMXpathこの例を確認してください


チェック: HTMLタグで正規表現を使用

0
Gilles Quenot