mk-mode BLOG

このブログは自作の自宅サーバに構築した Debian GNU/Linux で運用しています。
PC・サーバ構築等の話題を中心に公開しております。(クローンサイト: GitHub Pages

ブログ開設日2009-01-05
サーバ連続稼働時間
Reading...
Page View 合計
Reading...
今日
Reading...
昨日
Reading...

正規表現 - 文字列内から HTML タグを正確に判別!

[ プログラミング ] [ Ruby, 正規表現 ]

こんばんは。

使っているプログラミング言語に関わらず、文字列内から HTML タグの部分を抽出したり削除したりするケースがあると思います。

その際、正規表現を使用すると思いますが、場合によっては確実に HTML タグを判別できない場合があります。

結局、広く知れ渡っている正規表現パターンが正確に判別できますが、他のパターンも含めて数種類のパターンを Ruby で検証してみました。(正規表現の説明付きで)

0. 準備

今回のテストでは、以下のような文字列を考えてみることにする。
文字列をダブルクォーテーション " で囲んだり、シングルクォーテーション ' で囲んだり、タグの属性値内にさらにタグを埋め込んだりしている。

1
この画像<img src="../images/example.png" alt='example.png' title='<img>タグ例' /><span>テスト画像</span>です。

そして、テストはこの文字列から HTML タグを判別して除去することにする。
以下のような文字になれば成功ということ。

1
この画像はテスト画像です。

1. テストパターン1(不正確なパターン)

<.*?>

  • . は、改行を除く任意の1文字。
  • * は、直前の表現の0回以上の繰り返しだが、 *? とすることでさらに最短一致。

よって、 < と最初の > で囲まれた任意の0文字以上の文字列のこと。

2. テストパターン2(不正確なパターン)

<\/?[^>]*>

  • \/ は、 / 文字そのもの。 / だけだと別の意味なるのでエスケープしている。
  • ? は、 直前の正規表現の 0 または 1 回の繰り返し。
  • [ ] は、文字クラス指定。 [ ] 内に列挙したいずれかの1文字。
  • ^ は、 [ ] 内の先頭にあれば、 [ ] 内に列挙されている指定文字以外の1文字。
  • * は、直前の表現の0回以上の繰り返し。

よって、<> で囲まれ、 < の次に / が0個か1個で、残りが > 以外の文字列のこと。
言い換えれば、「テストパターン1」の <.*?> と同じになる。

3. テストパターン3(正確なパターン)

<("[^"]*"|'[^']*'|[^'">])*>

  • ( ) は、正規表現のグループ化。
  • | は、選択(OR)。
  • "[^"]*" は、 " と次の " で囲まれた部分という意味。
    これは、この部分を1つのかたまりとみなすため。
  • '[^']*' は、 ' と次の ' で囲まれた部分という意味。
    これは、この部分を1つのかたまりとみなすため。
  • [^'">] は、 ", ', > 以外の1文字。
    "" で囲まれた部分や '' で囲まれた部分を1つの固まりとみなしているのに、さらに "' が存在した場合は HTML タグではない。
    また、 > を除去するのは最短一致させるため。

4. テストパターン4(正確なパターン)

<(".*?"|'.*?'|[^'"])*?>

これは「テストパターン3」の正規表現を書き換えただけのものなので、これも正確に HTML タグを判別できる。

5. 検証用 Ruby スクリプト

上記の「テストパターン1」〜「テストパターン4」をテストする Ruby スクリプトは以下の通り。

test_regexp.rb
1
2
3
4
5
6
7
8
9
# -*- coding: utf-8 -*-

str = "この画像<img src=\"../images/example.png\" alt='example.png' title='<img>タグ例' />は<span>テスト画像</span>です。"

puts "[変換前] #{str}"
puts "[パターン1(×)] #{str.gsub(/<.*?>/, "")}"
puts "[パターン2(×)] #{str.gsub(/<\/?[^>]*>/, "")}"
puts "[パターン3(○)] #{str.gsub(/<("[^"]*"|'[^']*'|[^'">])*>/, "")}"
puts "[パターン4(○)] #{str.gsub(/<(".*?"|'.*?'|[^'"])*?>/, "")}"

文字列は "" で囲むので、文字列内の " は エスケープしている。

6. 検証実施

作成した検証用 Ruby スクリプトを実行してみる。

1
2
3
4
5
6
$ ruby test_regexp.rb
[変換前] この画像<img src="../images/example.png" alt='example.png' title='<img>タグ例' />は<span>テスト画像</span>です。
[パターン1(×)] この画像タグ例' />はテスト画像です。
[パターン2(×)] この画像タグ例' />はテスト画像です。
[パターン3(○)] この画像はテスト画像です。
[パターン4(○)] この画像はテスト画像です。

意図した結果となった。


他にも対応不可能なパターンがあるかも知れませんが、おそらく上記の「テストパターン3」、「テストパターン4」でほぼ確実に HTML タグを判定可能です。

また、正規表現の部分は Ruby に限らず他の言語でも応用可能です。
意外と詳しい説明(なぜそんな正規表現パターンなのかという説明)が少ないのでまとめてみた次第です。
参考になれば幸いです。

以上。

Comments