なぜ正規表現を使用してHTML / XMLを解析できないのか:素人の言葉による正式な説明


117

SOでは、(X)HTMLまたはXMLの解析に関する質問なしに、正規表現が要求される日はありません。

このタスクの正規表現の非実行可能性を実証する例や、概念を表す式のコレクションを思い付くのは比較的簡単ですが、SOでは、なぜこれが素人でできないのかについての正式な説明は見つかりませんでした。条項。

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

ここでの欠点は、HTMLがチョムスキータイプ2文法(文脈自由文法)であり、RegExがチョムスキータイプ3文法(正規表現)であることです。

または:

正規表現は正規言語にのみ一致しますが、HTMLは文脈自由言語です。

または:

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

または:

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

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

だから私の質問は:(X)HTML / XMLを解析するために正規表現を使用できない理由について、上記の正式な説明を素人の言葉で翻訳してくれませんか?

編集:最初の回答を読んだ後、明確にする必要があると思いました:翻訳しようとする概念も簡潔に説明する「翻訳」を探しいます:回答の終わりに、読者は大まかなアイデアを持っている必要があります-たとえば-「通常の言語」と「文脈自由文法」の意味...


19
コンピュータサイエンスの用語では、「正規表現」は現代の「正規表現実装」(プログラミング言語で使用するツール/ API)とは大きく異なることに注意してください。後者は、遭遇したことを「記憶」し、再帰的に定義された(サブ)パターンに一致することもできるため、理論的な「正規表現」よりもはるかに多く一致/解析/認識できます。
Bart Kiers、2011

1
@バート:これは本当に「正規表現」という用語を乱用する言語にのみ適用されます。POSIXEREは純粋に正規です。
R ... GitHubのSTOP手助けICE

2
@R ..したがって、POSIXを「現代の実装」と呼びます。すべての深刻さにかかわら:はい、あなたはそれらのは本当に正しいです定期的に。「...現代の正規表現実装の多く...」または「... PCRE正規表現実装...」と言ったはずです
Bart Kiers、2011

4
私は、無知なプログラマーに自分自身を売り込むために、厳密な言語を根本的に誤用しているプログラミング言語を真剣に受け入れるのに苦労しています...
R .. GitHubのSTOP手助けICE

3
@R ..、残念ながらPCREの実装は「正規表現」と呼ばれますが、言語を真剣に受け取らないと、IMOは一歩踏み込んでしまいます。つまり、あなたはPerl、Java、Python、Ruby、JavaScript、.NETなどをこれのために真剣に取っていないのですか?
Bart Kiers、2011

回答:


117

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

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

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

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

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


有限状態オートマトンのかなり良い説明もここにあります:youtube.com/watch
v

55

HTMLが通常の言語を表していないという事実は、赤いニシンです。正規表現と通常の言語は似ていますが、同じではありません。同じ起源を共有していますが、学術的な「通常の言語」とエンジンの現在のマッチング能力との間にはかなりの距離があります。実際、ほとんどすべての最新の正規表現エンジンは非正規機能をサポートしています-簡単な例は(.*)\1です。その文字の反復配列を一致させるためにバックリファレンスを使用しています-たとえば123123、またはbonbon。再帰的/バランスの取れた構造のマッチングにより、これらはさらに楽しくなります。

ウィキペディアはこれをラリー・ウォールの引用にうまく入れています:

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

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

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

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のすべてではなく一部を変更したい場合-私の知る限り、パーサーはこの要求に答えることができません。ドキュメント全体を解析し、ドキュメント全体を保存して、変更したくない部分を変更します。

4
これは、HTMLの解析に正規表現を使用する場合(使用しない場合)について非常に明確で適切に記述された部分ですが、私の質問に対する答えにはなりません。代わりに、この質問に移動することをお勧めしますか?私はそれがあなたにもっと評判を与えると思いますが-何よりも-それは将来の訪問者がそれをより関連性があると思う場所だと思います(「追加の力」を訪問者に思い出させる私の質問への@Bart Kiersによるコメントがあります現代の正規表現エンジンの)。
Mac

1
@mac-どうもありがとう。実は、私は少し考えました。私はあなたの質問に答えなかったのはわかっていますが、質問は基本的に正しいとは思いません-あなたは間違った理由を説明するように求めます...あなたは良い考えを持っていますが、おそらく他の質問の方がより適切です...
コビ

19

HTMLは無制限に入れ子に<tags><inside><tags and="<things><that><look></like></tags>"></inside></each></other>することができ、正規表現はHTML の子孫の履歴を追跡することができないため、実際には対応できません。

難しさを示す簡単な構成:

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

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


1
答えを感謝しますが、私の質問は「なぜ正規表現を使用できないのか...」ではありません。私の質問は、私が提供した正式な説明を「翻訳」することです!:)
mac

5
これはある意味でそれらすべての翻訳です。最も近いのは「正規表現は正規言語にのみ一致しますが、HTMLは文脈自由言語です」、有限オートマトンに関するものです。それは本当に同じ理由です。
Ianus Chiaroscuro、2011

申し訳ありませんが、多分私は私の質問を明確にしていません(それを改善するための提案は大歓迎です!)しかし、私はまた「翻訳」を説明する答えを探しています。あなたの答えは「通常の言語」も「文脈自由言語」の概念も明確にしません...
mac

5
これらの用語を説明することは、専門用語そのものと同じくらい技術的であり、すべての高精度言語が得ている実際の意味、それが私が投稿したものからの注意散漫になります。
Ianus Chiaroscuro、2011

4
<(\w+)(?:\s+\w+="[^"]*")*>(?R)*</\1>|[\w\s!']+コードサンプルと一致します。
Kobi

9

通常の言語は、有限状態機械で照合できる言語です。

(有限状態マシン、プッシュダウンマシン、チューリングマシンの理解は、基本的に4年次の大学CSコースのカリキュラムです。)

文字列 "hi"を認識する次のマシンを考えてみましょう。

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

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

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


2
「有限状態機械、プッシュダウン機械、チューリング機械を理解することは、基本的に300レベルのCSコースのカリキュラムです。」これはトピックの難易度を示すための試みであることは理解していますが、あなたが言及している学校のシステムに慣れていません。国別ではない方法で説明してください。ありがとうございました!:)
mac

1
更新しました。スタックオーバーフローの投稿で説明するだけでは理解が難しいことはわかりません。
Sean McMillan

6

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

XML、C、または言語要素の任意の入れ子を持つその他の言語を解析するには、自分の深さを覚えておく必要があります。つまり、中括弧/大括弧/タグを数えることができる必要があります。

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


6

文法とは、単語の行き先を正式に定義したものです。たとえば、形容詞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"");
    }
    ";
}

これは理解できる答えです
ある人

ただし、コンテキストフリーは、定期的なものではありません。一致する括弧の言語は文脈自由ですが、規則的ではありません。
Taemyr

追加する必要があるのは、正規表現(Perlにあるような拡張を追加しない限り)は通常の文法と同等であることです。つまり、任意に深くバランスがとれた括弧やHTML要素の開始タグと終了タグなど、任意に深くネストされた構造を記述することはできません。
reinierpost 2015年

4

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

たとえば、一致する正規表現を書くのは非常にうまくいきます

<price>10.65</price>

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

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

  • ドキュメントが名前空間にある場合は、任意の名前空間接頭辞を使用できるようにする必要があります

  • (特定の語彙のセマンティクスに応じて)開始タグにある不明な属性を許可および無視する可能性があります。

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

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

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

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


2

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

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

たとえば、

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

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

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

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

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

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

要するに、XMLパーサーを実際の作業に使用し、正規表現をいじりたい場合はこれを使用してください。


3
この正規表現は、入力が整形式である場合にのみ一致するというステートメントは正しくありません。これは、名前が有効なXML名であることを確認しません。属性を確認しません。エンティティと文字の参照を確認しません。CDATAや処理命令を処理しません。テスト済みであると言うと、XML適合性テストスイートに似たものでテストされていることは間違いありません。これは、私が今まで見た正規表現でXMLを処理しようとするすべての試みの問題です。それらは少数の入力で動作しますが、アプリケーションに合法的に渡すことができるXMLでは動作しません。
マイケルケイ

2
また、正規表現が一致しない整形式の入力があります。たとえば、終了タグの名前の後の空白は許可されません。これらのグリッチのほとんどは簡単に修正できますが、すべてのグリッチを修正すると、完全に使用できないものになります。そしてもちろん、本当の難点は、パーサーに「はい/いいえ」の答えを与えるだけではなく、何か有用なことを行うアプリケーションに情報を渡すことです。
マイケル・ケイ

0

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

理論:

コンパイル理論によれば、有限状態機械に基づく正規表現を使用してXML / HTMLを解析することはできません。XML / HTMLの階層構造のため、プッシュダウンオートマトンを使用し、YACCなどのツールを使用してLALR文法を操作する必要があります。

realLife©®™日常のツール

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

xmllintは、デフォルトでlibxml2、xpath1 とともにインストールされることが多い(改行で区切られた出力があるようにラッパーを確認する

xmlstarletは編集、選択、変換ができます...デフォルトではインストールされていません、xpath1

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

xidel xpath3

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

または、高水準言語と適切なライブラリを使用できます。

lxmlfrom lxml import etree

「S 、XML::LibXML、、XML::XPathXML::Twig::XPathHTML::TreeBuilder::XPath

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

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


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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.