藤原順一のブログ

彼が過ごした日々をこちらに綴る

このブログが作られるまでの過程その2ー正規表現を使ってみた

最終更新: 2020/8/22 8:55:21
正直、これを使いたい気がしなかった。でも一度使ってみたらより効率よくコードを書ける気がした。
これは記事原文を解析するためのエンジンを作っている話なんですが、ふとあることに気づいた。もしただ改行(かいぎょう)を入れたいだけなら、本当に#parastartを#rawstartに変更してすべてをHTMLで書かなければだめなのか?と考えてしまった。それともあのブロックを閉じて#rawstartで改行したあとを文字を入力するのが?とも考えてしまった。この2つの方法がありとは思ったけど、ちょっと面倒くさいと思った。そして、2つ目の方法を使うと文字の改行が改段落に見えてきてなんか嫌な気がした。
色々考えてみたが、入力されたテキストにあるすべての改行を<br/>にするのがめっちゃ楽だけど、一部のエディターが文字が折り返されなくて編集するときめっちゃ疲れる。最後、いろんなフォーラムに使われたBBCodeみたいなものを導入することにした。このシステムに使われたタグは2つの形式があるんだ。

1. [(タグ名)=<(引数)>]...[/(タグ名)](例:[link=/]ホーム[/link])
2. [(タグ名)/](例:[break/])
詳細のタグの使い方については次回で説明しますが、今回ではどうやって解析するのかを説明します。
最初はこのようなコードでなんとかできたんだけど、タグの数が増えるほど管理が難しくなった。
paragraphs.filter(x=>!x.useRaw&&x.text.indexOf("[link=")!==-1).forEach((e,i)=>{
  let tmp="",str=e.text;
  let idx=str.indexOf("[link="),idx2=str.indexOf("]"),idx3=str.indexOf("[/link]");
  while(idx>=0){
  if(idx2===-1){
   reject("Syntax error at paragraph ${i+1}");
   return;
  }
  if(idx3===-1){
   reject("Missing ending [/link]");
   return;
  }
  tmp+=str.slice(0,idx);
  let link=str.slice(idx+6,idx2);
  let text=str.slice(idx2+1,idx3);
  str=str.slice(idx3+7);
  tmp+=`<a href=${link}>${text}</a>`;
  idx=str.indexOf("[link=");
  idx2=str.indexOf("]");
  idx3=str.indexOf("[/link]");
  }
  e.useRaw=true;
  e.text=tmp+str;
});
管理するの難しさ以外にこのコードでは想定外の入力だとバグが生じやすいんだ。例えば、もしx.text="[link = http://www.example.com]Example[/link]"だとしたら、このコードでは正しいタグと認識されないんです。
もしこのようなコードが管理しにくいのだったらどうやって管理しやすくするのか?それは正規表現だ。ウィキペディアによれば正規表現は文字列の集合を一つの文字列で表現する方法の一つである。これを使えばさっきの状況になりにくくなるんだ。
じゃーさっきの文字列を解析しょう。
まず、表現の基本形をとって解析する。
するとさっきの表現がこのように見えるんだ
[link<s>=<s><リンク>]<文字>[/link]
注:s=空白があるかどうかはどうでもいい
そして、これを正規表現に変更してみた。
\[link=\s*(\[ ^\ ]]+)\s*\]([^\[ ]+)\[\/link\]
これなら空白があっても問題がならないはずだが問題が文字の中に"["があるんだとしたら認識されなくなる。
これを解決するにはエスケープ文字を使わなければだめみたいんだ。簡単に言うと特殊文字の前に"\"を入れると、解析エンジンに何もせずにそのまま出力に出すことになる。じゃー、これ実装するには"]"を禁じて、"\]"を許可しなければなりません。この難題を解決するには私が半時間ほどネットから情報を探して自分なりに答えを出してみた。実際、否定後読みを使えば解決になるんだ。
\[link=\s*([^\]]+)\s*\]((?: (?! (?:(?<!\\) \[ ) ). )+ )\[\/link\]
ちょっと分かりづらいと思ったので、説明します。
一番中にある"(?:(?<!\\)\[ )"はすべて先頭に"\"がない"["をマッチする
そして、あれを囲む"(?!.....)"はあれにマッチする文字がないという条件を指定する
最後に、一番外の"((?:(.....).)+)"は先頭に"\"がない"["を含む全ての文字にマッチしてそしてキャプチャする。
ちょっと上達するには時間かかるんだけど、上達するよる効率よくコードを書ける気がした。
タグ: プログラミングJavascriptブログシステム開発
<<次の記事前の記事へ>>

作者について

藤原順一

藤原順一

2000にマレーシアで生まれ男で現在デジタルマルチメディアを勉強しています