Stringクラスに対する拡張(1)

【抜粋】一部省略
Object.extend(String.prototype, {
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },
(省略)
  inspect: function() {
    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
  }
});

既存Stringクラスにメソッドを追加しています。

stripTagsメソッド

【抜粋】
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

文字列(this)から、タグ部分を削除しています。

正しく取り除けない場合

この正規表現だと正しくタグを取り除けない場合があります。

【例】
alert("<input type='text' value='>'/>TEST".stripTags());
//「TEST」でなく、「'/>TEST」と表示されてしまう。

これに対応させるためには以下のようにします。

【修正案1】(これでもダメな場合があります。後述。)
  stripTags: function() {
    return this.replace(/<\/?[^"'>]*(?:(?:"[^"]*"|'[^']*')[^"'>]*)*\/?>/gi, '');
  },

「ループ展開」という技法により記述されています。参考にしたのは以下です。

正規表現辞典 (DESKTOP REFERENCE)
05-02-05 HTML/XMLの開始タグにマッチさせたい

この出展はさらに「詳説 正規表現 第2版」にあるようです。

厳密にタグを取り除く

上記だと以下の場合に正しく取り除けません。

【例】
alert('<input type="text" value="\\">"/>TEST'.stripTags());
//「TEST」でなく、「"/>TEST」と表示されてしまう。

このケースにも対応させてみました。

【修正案】
  stripTags: function() {
    return str.replace(/<\/?[^"'>]*(?:(?:"(?:\\"|[^"])*"|'(?:\\'|[^'])*')[^"'>]*)*\/?>/gi, '');
  },

たぶんこれでいけると思うのですが。まぁ、素直にエスケープさせとけ、という話ではあります^^;

stripScripts

【抜粋】
  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

文字列(this)から、scriptブロックを削除するメソッドです。前述PrototypeオブジェクトのScriptFragmentプロパティからscriptブロックの正規表現文字列を取得しています。

replaceメソッド第二引数のm修飾子は、正規表現マルチラインモードにするためのものですが、Prototype.ScriptFragmentには「^」「$」が使われていないので意味がない気がします。

【参考】正規表現 マルチラインモード
var str = "1_line\n2_line";

alert(str); //1_line
            //2_line と表示(\nは改行)

var matches = str.match(/^2_line/); 
alert(matches); //'null'と表示される。マッチしてない。

matches = str.match(/^2_line/m); //マルチラインモード
alert(matches); //'2_line'と表示される。「^」が行頭にマッチした。

extractScriptsメソッド

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

文字列(this)のすべてのscriptブロックの内容を、配列に格納して返却するメソッドです。

Prototype.ScriptFragmentを使用して、グローバルマッチする正規表現オブジェクトと、シングルマッチする正規表現オブジェクトを作成しています。m修飾子は無意味な気がします。

少しややこしいですが、文字列(this)からすべてのscriptブロックを抽出し、更にそれぞれからscriptブロックの内容(コード部分)を抽出して配列に格納し、返却しています。

mapメソッドはArrayクラスに追加されているメソッドです*1。引数に指定された関数を、配列要素すべてを順番に引数に指定して実行し、結果を配列に格納して返却します。

以下の一行はscriptブロックの内容を抜き出して返却しています。マッチしなければ空文字(['', '']の2番目の'')を返却しますが、このケースは発生しない気がします。

【抜粋】
return (scriptTag.match(matchOne) || ['', ''])[1];

シングルモードのマッチングの場合でも、返却されるのは配列です。インデックス0にマッチした全体の文字列、インデックス1以降に、「(pattern)」でグルーピングした部分にマッチした文字列が順次格納されます。ただし、「(」の直後に「?:」があると後方参照不可となり、格納されません。

【参考】正規表現 シングルモードのマッチング
var str = "aaabbbcccdddeee";
var matches = str.match(/a+(b+)(?:c+)(d+)/);
alert(matches[0]); //"aaabbbcccddd"と表示される
alert(matches[1]); //"bbb"と表示される
alert(matches[2]); //"ddd"と表示される
//参考 RegExpオブジェクトによる取得
alert(RegExp.$1); //"bbb"と表示される
alert(RegExp.$2); //"ddd"と表示される

*2

正規表現についての参考文献

正規表現の説明はあまり詳しくはしません(というかできません^^;)。他サイトや文献を参考にしてください。現在筆者が参考にしているのは以下です。(他書との比較検討はしていませんが、とりあえずやりたいことを調べる時には重宝しています)

正規表現辞典 (DESKTOP REFERENCE)

*1:Enumerableクラスからの継承メソッド

*2:RegExpオブジェクトは$9までしか扱えませんが、返却された配列を使うとその制限はありません。