JavaScript の 正規表現replace でネストしたdivを平坦化(flatten)する, 小要素のないdivを消す
もっといい正規表現でのやり方があったら教えてください。
npm パッケージとか使えばスッとできそうなんですが
ブックマークレットにCDNから読み込みたくなかっただけです。
TL;DR
- ネストしたdivを平坦化(flatten)
-
targetString.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
を何度か繰り返す
-
- 小要素のないdivを消す
-
targetString.replace(/<div>\s*<div>([^>]*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
を何度か繰り返す
-
なぜやったのか
ブックマークレットを作っていて
- ネストしたdivを平坦化(flatten)
- 小要素のないdivを消す
をreplaceでどうにかしたかったので、やりました。
どうやってそれぞれの正規表現に行き着いたか
正規表現チェッカーを使いながらいい感じのを考えました笑
オススメの正規表現チェッカーはこの2つです。
- refular expressions 101
- 正規表現置換チェッカー(JavaScript版) | Softel labs
- JavaScript の正規表現チェッカー2選 | 北山淳也 | zenn
ネストしたdivを平坦化(flatten)
まず平坦化と言う表現が合ってるのかわからないのですが、
例を挙げると
こういうHTMLを
<div><div><div><div>foo</div></div></div></div>
<div>
<div>
<div><div>bar</div></div>
</div>
</div>
<div><div>baz</div></div>
こうしたかったという話です。
<div>foo</div>
<div>bar</div>
<div>baz</div>
最終的に行き着いたのは
targetString.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
を何度か繰り返す
という方法で、コードとしてはこうしました。汚ねぇ……😂
targetString
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
.replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>");
他にも
targetString
.replace(/<div>\s*<div>\s*<div>(.*)<\/div>\s*<\/div>\s*<\/div>/gs, "<div>$1</div>");
こんな感じで複数ネストを一気に単純 div 囲いに変換もできるのですが
この正規表現だとネスト数分のパターン用意しないといけないので、
何度か繰り返すことでまぁ大体大丈夫だろってところまでネストをなくすことにしました。
2020/12/23 追記
@catnose さん からコメントいただきまして、
replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
の部分ですが、これだとネストされているときに閉じタグ(</div>
)がずれてマッチされてしまうことがあるようです。
なので
text.replace(/<div>\s*<div>([^>]*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
とのことです。
while
を使った方法も提示してくださっているのでコメント欄も合わせてみてみてください
replace の replace(/../gs,'')
この部分の s ですが
dotAll という ES2018 以降のオプションです。Can i use .. をみる限り大体のブラウザで既に対応されてそう。
s ("dotAll") フラグが true にセットされている場合は、改行文字にもマッチします。
- 正規表現 - JavaScript | MDN
- JavaScript built-in: RegExp: dotAll | Can i use
平坦化(flatten) という表現は
多次元配列を単次元配列に変換する平坦化(flatten)とちょっと似てるなと思ったからです……。
- Ruby 2.7.0 リファレンスマニュアル ライブラリ一覧 組み込みライブラリ Arrayクラス flatten
- Array.prototype.flat() | MDN
- Pythonでflatten(多次元リストを一次元に平坦化) | note.nkmk.me
小要素のないdivを消す
<div></div>
<div><div></div></div>
<div><div><div></div></div></div>
こういうのを消し去りたかった。
行き着いた正規表現は
targetString.replace(/<div>\s*<\/div>/g, "")
を何度か繰り返す
で、コードとしてはこうしました。これも汚ねぇ……😂
targetString
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "")
.replace(/<div>\s*<\/div>/g, "");
これもネストしている分まとめて複数div分を置換できるのですが
愚直にやった方がシンプルな気がしたのでこうしてます。
もっといいやり方があったら教えてください〜😂
Discussion
たしかに簡単そうで「あれ、どうやるんだ…?」ってなりますね。「ネストしたdivを平坦化」の方に取り組んでみました。
あまり綺麗とは言えない & HTML構造に間違いがあると無限ループの可能性があるので怖いですが…。
replace(/<div>\s*<div>(.*)<\/div>\s*<\/div>/gs, "<div>$1</div>")
の部分ですが、これだとネストされているときに閉じタグ(</div>
)がずれてマッチされてしまうことがあるようです。なのでのようにすると良いと思います!
@catnose さん
ワオ!ありがとうございます!
PCRE ならもっとエレガントにできるのでは…
と思ったけど,再帰フェーズに入ったときの後方参照拾えなかったのでダメでしたw
供養します