変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

JavaScript

[javascript]正規表現を正規表現で表現できるかな

正規表現自体・・・

呪文

と呼ばれているので

下のテキストから正規表現の部分をパースしなさいとかは、難題

(* 文法   *)
        syntax      =   rule_list /\s*/ ;
        rule_list   =   { ( rule | special_effect | comment ) } + ;
        rule        =   identifier defining_symbol definitions ";" ;
        defining_symbol = "=" | "::=" ;                                     (* 終端記号 '=':EBNF, '::=':BNF *)
        (* 定義   *)
        definitions =   definition { "|" definition } ;                     (* 定義リスト *)
        definition  =   choice ;                                            (* 定義 *)
        (* リスト *)
        choice      =   sequence { "|" sequence } ;                         (* アレかコレかソレかのどれか *)
        sequence    =   exception { [ "," ] exception } ;          		    (* アレとコレとソレの一式。","は空白改行と同義  *)
        exception   =   words, { "-" words } ;          			        (* アレが良いが、コレとソレを除く。 *)
        (* 語彙   *)
        words       =   [ comments ] word [ comments ] ;
        special_effect =  (*$ NO SPACE SKIP $*) "(*$" /[^\\?]*/ "$*)" ;     (* 特殊効果 *)
        comments    =   ( special_effect | comment ) repeat_symbol;
        word        =   ( sq_string | wq_string | wraper | special_text | regexp | identifier ) ;
        comment     =   /(?:\(*))(?![*][/])(.*)(?:*\))/ ;                   (* 注意書き *)
        sq_string   =   /(?:')(?!\\\\)[^']*(?:')/ ;                         (* ' で括られたテキスト *)
        wq_string   =   /(?:")(?!\\\\)[^"]*(?:")/ ;                         (* " で括られたテキスト *)
        bq_string   =   /(?:\`)(?!\\\\)[^"]*(?:\`)/ ;                       (* " で括られたテキスト *)
        (* 囲み   *)
        wraper      =   ( group | option | repeate ) repeat_symbol ;
        group       =   "(" choice ")"  ;                                   (* choiceを明示的に括る表現 *)
        option      =   "[" choice "]"  ;                                   (* あれば尚良し *)
        repeate     =   "{" choice "}"  ;                                   (* 0回以上繰り返す *: 0回以上、+:1回以上 *)    (* TODO choiceを使うと exceptionで[undefined]が返る *)
        regexp      =   /.+/([a-z]*)  ;      
        identifier  =   /[A-Za-z][A-Z_a-z0-9\\x20]*[A-Z_a-z0-9]*/;          (* 識別子 *)
        special_text =  (*$ NO SPACE SKIP $*) "?" /[^\\?]*/ "?" ;           (* 特殊文字列 *)
        repeat_symbol=  [ "*" | "+" | '{' [ number ] ',' [ number ] '}'];   (* 繰返し記号 *)
        number      =   /\d+/ ;

なので

regexp  =  /.+/([a-z]*) ;

とか表記は誤魔化してたけど、

実際にはこれでパースするとテキストの終りの手前までヒットしてしまう。

とある記事で、エスケープ表現の文字を潜り抜けさせる方法が載ってた。

 (¥¥@)[^@]+   ※@は任意の文字

な感じで@のエスケープ表現の2文字@以外の文字のいづれかを繰り返す表現をしていた。

これを真似て

/ ( ( \ / ( ( \ \ / ) | ( [ \ / ) | [^\ / ] ) * \ / ) ( [ a – z ] * ) ) /

/ \ / ( ( \ / ( ( \ \ \ / ) | ( \ [ \ / ) | ( \ ( \ / ) | ( [ ^ / ] ) ) * \ / ) ( [ a – z ] * ) ) /  ※壊れてたので訂正 2025/5/6

※ps.2025/5/8 さっぱり通らなくなることはタマにある。が起きたので再訂正、正規表現デバッガもコピーボタンも2通りの書き方を出力するように変更(DevToolsを開きキャッシュクリア必須)。やはりコノPC何かが変なんだろうね?

const re1 = new RegExp("(\\/((\\\\\\/)|(\\[\\/)|(\\(\\/)|([^/]))*\\/)([a-z]*)", "gim");
const re2 = /(\/((\\\/)|(\[\/)|(\(\/)|([^/]))*\/)([a-z]*)/gim;

※崩れた顔文字ではありません、読みにくいので空白を挿入しました。

を作って試してみた結果は

うまくいってるみたいだが、ヒットした正規表現の半分は顔文字にしか見えない。

しかも、長い。長すぎる~。

ps.2025/5/9

MDNでreplace系で使う置換する関数に渡るパラメータは

replace(/(/{2}.*$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)/, replacer);
👇
function replacer(match, p1, p2, /* …, */ pN, offset, string ) {
  return replacement;
}

で、「名前付きのキャプチャグループ」がある場合はgroupsが付加されて

replace(/(?<COMMENT>/{2}.*$|\/\*\/?([^/]|[^*]\/|\r|\n)*\*\/)/, replacer);
👇
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) {
  return replacement;
}

となっていたので、名無しの正規表現でもそれなりに色付けできるように上書き修正

普通に

function replacer(match, p1, p2,  … pN, offset, string, groups) {
//  … pN 部分は全部pNに含まれる(だったらいいな的な
  return replacement;
}

みたいに…(残余引数)が使えたらいいけどね。

ps.2025/5/18

自身の表記が通らなかったので、

・・・|(\^\/\])|・・・

を追記したら改行が通ってしまうので

[^/] を
[^/\r\n]に変更

して

/(\/((\\\/)|(\^\/\])|(\[\/)|(\(\/)|([^/\r\n]))*\/)([dgimsuvy]*)/

になったけど、BNFのテキストの正規表現内に

\r\n

があるとダメなのは当然なので

\\r\\n

と表記しても通らない?

原因がよく解らないなぁと思ったら \が通らなくなっていた。

[^/\r\n]は、 / と¥とrとnを除外する意味と\r\nを除外する2つの意味を持ってしまっていた。

色々試してみた結果

x ([^/\r\n])
▲ ([^\r\n/])   \]っぽく処理されてダメだった
▲ ([^/]|[^\r\n]) 論理和だから条件が甘アマ
◎([^/\x0a\x0d]) 非推奨だけど

らしい(微妙

その後も「*/」のパターンが抜けてたり追記する度にエスケープさせるパターンが増える罠に遭遇し続けた結果がコレ

/(\/((\\\/)|(\^\/)|(\[\/)|(\(\/)|(\*\/)|([^/\x0a\x0d]))*\/)([dgimsuvy]*)/

自身はちゃんと判定できるものの、「*/」は末端の「/」と判定しないから、

「/.*/aaaaaaa/」は、「/.*/aaaaaaa/」な正規表現と判定される訳で

正規表現はパーサコンビネーションで解消するしかないっぽい。

そうなるとまた際限がないので、

正規表現を

  • / で始まり
  • 特殊な文字「( ) { } [] . + *等」か その他の文字 の繰り返し
  • / で終わる

の様な表現で括らないとダメな気がする。

/(\/)((\\/)|([^/\x0a\x0d]))+(\/)([dgimsuvy]*)/

のあたりで妥協して

EBNFで使うには文法上の利用方法を見直した方が良さそう。

でも、正規表現の文字クラスの中の文字クラスとかグループとか繰り返しとかを内包しないみたいなんで、[…]は/有無に関わらず通しても良さそう

/(\/)(((\\\/)|(\[.*?\])|([^/\r\n\s]))+)?(\/)([dgimsuvy]*)/gmuy

で、何とかならないかな?

2か所で使っている最短マッチング(x?y)が微妙で、chromeでもnode.jsでも

1つ目はグループを入れて見やすくしたり手を入れると、通常の最長マッチングになり

◎ (\[.*?\])
× ((\[.*)?(\])) xというか通常の最長マッチングになる場合がある

文末に近いコメントの中の ] までマッチングしてしまうが、

/(\\/)((\\\\/)|((\[.*)?(\]))|([^/]))+(\\/)(dgimsuvy*)/  ; (* 正規表現されたテキスト  + /.+/ "/" + { /[dgimsuvy]/ } *)

2つ目は(…)?(\/)とグループにして入れても支障は無い。

どうやら(…)で括ると \[ [ みたいに階層無しに思え最長マッチング(つまり通常のマッチング)してる気もするが([.*?])単体では最短マッチングするので、最短マッチングを階層の上と下で使う分には問題ないけど、その両方の?の両辺をグループにしてしまうとマッチングの位置(position)をうっかり共有してしまってる(VMの変数のネーミングが被ってるかVMのコンパイラには同じに見えている)気がする。

それに、VScodeのjsファイルのエディタで(\\\/)部分がダメで(\\\\/)で通る様にしたのが上の正規表現だったりするので、雑な正規表現は微妙な動きをするのは仕方が無いのかもしれない。



【javascript】 RegExp 正規表現

よく ”20161216010203″.replace(/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})/g,”$1/$2/$3 $4:$5:$6″);
などどダラダラな使い方をしてしまうと
もう正規表現とはいいがたい。
var reg = new RegExp(”\(\[0-9\]\{4\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)\(\[0-9\]\{2\}\)”, “g”);
var mat = reg.exec(“20161216010203”);
しかもパターンをjavascriptのリテラルとして指定するとエスケープシーケンスで更に長くなって訳が分からなくなる。
字句解析した部分:mat[0]、年:mat[1]、月:mat[2]、時:mat[3]、分:mat[4]、秒:mat[5]
など・・・となるはずなんだけどなぁ・・・
その度にスクラッチするのも面倒なのでデバッガを作ってみた。

ちまちましたパターンのデバッグに丁度いい。

複数のパターンを書けば、 | で繋いで、字句解析する様になっているものの・・・
パターンの長いものを先に書かないと、短いパターンが優先してマッチングしてしまい不調。
各パターンが括弧で括る(・・・)なパターンを想定しているので、上の様な場合は、複数のパターンを書くと、うまくいかないので、
デバッグ・ログ出力のチェックボックスを追加した。
これで、$0,$1,$2・・・$n の出力の雰囲気も判るハズ。

で、VisualStudioのC#のForm1.Designer.csを読取り
1つづつトークンと正規表現を増やしていく・・・
COMMENT:    (/{2}.*$|/\*.*\*/)
REGION_B:   (\s*#region\s+[a-zA-Z0-9]+.*$)
REGION_E:   (\s*#endregion$)
SPACE:      ([\s]+)
L-BRACE:    (\{)
R-BRACE:    (\})
L-PAREN:    (\()
R-PAREN:    (\))
COMMMA:     (\,)
DOT:        (\.)
BINOP:      (\|\||&&|!=|==|\+|<=|>=|-|\*|\/|%|=)
DELIMITER:  (;)
NULL:       (null)
VALUE_TYPE: (void|integer|bool|string)
SCOPE:      (public|private|friend|static)
THIS:       (this)
NEW:        (new)
BOOL:       (true|false)
SYMBOLE:    ([a-zA-Z0-9]+)
NUMBER:     ([1-9]\.?[0-9]+)
この辺から急に重くなりだす。
暫くすると
メモリーガーとか言い出す。
LITERAL:    (‘[^’]*’)
WQLITERAL:  (“[^”]*”)
まで来ると、
字句解析させてみるとタイムアウト。
matchならあっという間などど思ってたら、
実は,解析結果の出力方法が・・・
for ( var idx in tokens ) {
log( idx + ” type:” + tokens[idx].type + “, token:” + tokens[idx].token );
}
だったせい。
このlog(msg)は間に合わせに作ったログの書き出し処理で
中身は・・・
function log(msg) {
$(‘#result’).text($(‘#result’).text() + msg + “\n”);
}
Form1.Designer.csを字句解析すると7000個以上のトークンに分解できるが
そこまではexecもmatchに瞬時。
しかし、DOM要素を7000回も読み書きしていたので
とても時間がかかっていたのた。 orz
ついでだから50%増速モードに変更。
var msg=””;
for (var idx = 0; idx < tokens.length; idx++) {
msg += idx + ” type:” + tokens[idx].type + “, token:” + tokens[idx].token +”\n”;
}
log(msg);
これで、劇的に早くなりデバッグも容易になった。(大笑
ま、トークンが22種類になり$1~$22となってしまうと、$9までの制限があるreplace( ) はそのままでは使えなくなるけどね。
とても、がっがりしたけど、
Form1.Designer.csの字句解析まではサクっと終わるから、
後はちゃんと文法にまとめ、HTMLのフォームに変換すれば、VisualStudioのC#のWindowsFormフォームをブラウザに写し取れるハズ。
勿論、その逆も可能なハズ。
※実はここが重要だったりする。
と云うのも、VisualStudioのフォーム・エディタで編集するとプロパティをちょっと追加しただけでテキストの順序が結構移動するので、svnの差分エディタを見るとウンザリする差分ができあがるのだ。
ここだけちょっと何とかしたい。
華麗でなくていい、綺麗にそれなりに自力でソートできるだけでも十分実用的じゃない?
でも、出来上がった正規表現パターンは訳が判らないなぁ~(爆笑
to be continue…



【JavaScript】 IFRAME

うかつに使うとCSSやコードページが設定できず痛い思いをするIFRAME。
サンプル
urlを打ち込んで【Add IFRAME】ボタンを押すと小窓が開く。
urlが空ならTextAreaが出てくる。
localStrageでurlもTextAreaの中身も覚えるので便利に見えるかもしれない。
かといって、いつまでの残っていると困るので、All IFRAME erase で全部忘れる。



【JavaScript】Selectors API

たまにjQueryを使うようになると、画面の中の必要なデータをJSON形式にまとめて送信するのが一番楽で、初期表示もJSON形式にまとまったデータを展開する方が簡単だ。こうなると、servletとJavBeanとJSPとタグライブラリィを使って器用にHTMLをでっち上げるMVPなんて「手間がかかる無駄飯食い(GUI)」でしかない。
MVPってホスト全盛だった前世期の『構造化プログラミング』であって、今風のアジャイルな思い付きの「変更後しか画面に出てなかったけど、変更前と変更後の内容を併記する」という様な、ネストの差替えが伴う仕様変更には滅法弱い。
Beanは丸ごと下位のクラスに押し出して、元のクラスのメンバーにし、servletは丸ごとリファクタリングしてループすればいいけど
画面に直結するJSPはサクっと直すとダラダラと長いコードにしかならないので書き直すしかないのは、ホスト全盛だった前世期と同じだ。
jQueryは、IEを使うと、JavaScriptでデータを展開した結果のHTMLを見るのは難しいけど、FireFoxでCtrl+Aで全画面を選択し右クリックで「選択した部分のソースを表示(E)」すれば見れる。
しかし、JUnit系のテストツールでは、HttpRequestが送ったHTMLコードを保存する様で、画面に展開した後のHTMLはファイルとして残らない。
ところが、jQueryを使えば、「classごとにイベントをまとめて登録することが常識」と云うことで押し切れる世の中であるが、何でもかんでも「classごとにイベントをまとめて登録すること」になってしまうところが『常識』の恐ろしい処でもある。(笑
そのjQueryを使わなくても、ブラウザのSelectors APIで十分になったらしい。
既存のAPIとの違いと云えば、 APIに渡すパラメータに疑似クラス名が使え、API で返される内容がリストからイテレータになっている様で、~. style.bold = true;  参照すれば、要素の分だけ、勝手にループしてくれるので、簡単なことは簡単に書ける。
但し、boldもitalicもunderscoreもfontのポイントも変えたいとか色々変える場合は不向きな感じがするから、cssを絡めクラス名を差し替える様な「短いコード」にした方がいいだろう。
「短いコード」で十分なら、jQueryも特に使わなくても良さそうだ。※非同期通信は使用しない条件付きで…
そうすることで、FireFoxでオブジェクトを選択してソースを見れば、classの内容で、仕様通りのクラス名に変わっているハズなので、デバッグが捗るというものだ。
振り返ってみると、Java系WEB Serverの仕様って、本当に「黒歴史」ばかりなのには・・・驚きを隠せない。



[JavaScript] LocaleStrageのサンプル

長らくブラウザのクライアントで記録するものと云えばクッキー(cookie)しかなかった。
記憶容量も4KBぐらいまでだった。
今ではLocalStrageという変数がdocumentのurlのドメインごとに記憶してくれる。
でも5MBぐらいまでらしい。
なので、サンプル(あて名書き)を作ってみた。 あて名を書くだけのメモ。
※htmlファイルをパソコンの同じフォルダにダウンロードしても使用可能。ただウチでは「iden_bs.js」というファイルが混入してくる。
先のJSFIDDLEも、一見サーバーに保持している様に思えるが、実はLocalStrageでセルの値や式を保存している。
パソコンの方で情報を記憶しているので、ネットが繋がらなくても、暫くの間は記憶してくれる。
でも、いつ何時消えてしまうか判らないので、この手のモノは皆CSVの出力と読込みは必須な気がするのでCSVの出力と読み込みも付けてみた。
CSVを読込むとLocalStrageを全部設定しなおすので列名は好きに替えられるので使い道は案外あるのかもしれない。
とは云え、LocalStrageのセキュリティ上の安全性はよく解りません。
大切な情報のメモには使えませんね。(大笑
また、CSVをSJISで出力するにはコード変換テーブルを組み込まないと無理というのが定説らしいけど、コード変換テーブルさえ作れれば何とかなる。それが あて名書き2 だ。
トンでもなく量のコード表をjsファイルに埋込むのが嫌で、JavaScriptでUint8ArrayとUint16ArrayでSJISのコード表を作ってみた。
これをBlob形式にして JavaScriptのFileReader .readAsText(b,”SJIS”) で読取らせれば 何気にUTF16テキストに変換してくれるので、元のSJISコード順さえ覚えているなら、Key:UTF16コード、Value:SJISコードなマップを作れる。
うん、readAsTextで指定できる文字エンコーディングには

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

のcharsetで指定できるものなら使えるらしいので、EUC や JIS も使えそう。
先のマップを使ってUTF16のテキストをUint16Arrayに変換すれば、バイナリーデータをダウンロードするコードでSJISコードページのテキストファイルに保存できる。但し、文字サイズが16ビット固定になってしまうハズなので・・・MS Office 365のEXCELで読めるけど古いバージョンでも読めるのかは不明。
またCSVのデリミタはタブ(\t)からカンマ(、)に変えてある。
しかし、数万回ループするので、ローコストなパソコンやスマフォでは、あて名書き2 は開かない方がいいかもしれない。
一応、SJISコード表をダウンロードするコードも残して置いたのでPopupWindowに挿しこんで自動的に読むようにしてみたり、コード表をLocalStrage に保存しておけば、2度目に開いた時は遅くないかもしれない。
SJISは、使用不可な文字領域が多いので、8ビット版 と 2区画(8140~9ffc  、e040~effc、下位バイトが7fの場合を除く )の 16ビット版 で作成したけど、足りないのかもしれない!(大笑
追加
あて名書き3

  • IFRAMEにテーブルを収めてみた。※関連ソースは思いっきりFireFox系なのでIEやChromeでは動かなさそう。
  • ダウンロード用のコード変換部分をsjis.jsに分離。

document.createElementでIFRAMEにタグを書いても
IFRAMEの中は
<html><head></head><body></body></html>
という文字列にしかならなかったので、

iframe.contentDocument.open();
iframe.contentDocument.write(' ');
iframe.contentDocument.close();

でHTMLの基本的なタグを作ってもらう。
でも、dummy.htmlの<link rel=”stylesheet” href=”atenagaki.css” type=”text/css” /> が無駄になり、JavaScriptでまんま追記する。

var link = document.createElement('link');
link.rel='stylesheet';
link.href='atenagaki.css';
link.type='text/css';
iframe.contentDocument.querySelector('head').appendChild(link);

無駄すぎる。

  • 半角文字が空白+半角のままだったので、無駄な空白を取った。
  • CSV読み込み時、CSVのデリミタ(カンマかタブ)、改行(CrLf,Lf,Cr)を判定。

後はUTF8やSJIS、EUCを自動判定できればいいかな。




top