質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

2回答

1404閲覧

合成除外の文字以外で構成された文字列の置換を正規表現で行いたい

sounisi5011

総合スコア697

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

0クリップ

投稿2015/11/28 10:59

編集2015/11/28 14:40

JavaScriptで、合成除外以外の文字に対してUnicode正規化を行う処理を書こうと考えています。
その際、正規表現で「合成除外の文字以外の文字列」にマッチする正規表現をどのように記述すれば良いか分からなくなったため、質問させて頂きます。

まず、合成除外の文字にマッチする正規表現は以下になります。
(こちらのパターンmathiasbynens/regenerateというライブラリを用いて生成したものです)

JavaScript

1/[\u0340\u0341\u0343\u0344\u0374\u037E\u0387\u0958-\u095F\u09DC\u09DD\u09DF\u0A33\u0A36\u0A59-\u0A5B\u0A5E\u0B5C\u0B5D\u0F43\u0F4D\u0F52\u0F57\u0F5C\u0F69\u0F73\u0F75\u0F76\u0F78\u0F81\u0F93\u0F9D\u0FA2\u0FA7\u0FAC\u0FB9\u1F71\u1F73\u1F75\u1F77\u1F79\u1F7B\u1F7D\u1FBB\u1FBE\u1FC9\u1FCB\u1FD3\u1FDB\u1FE3\u1FEB\u1FEE\u1FEF\u1FF9\u1FFB\u1FFD\u2000\u2001\u2126\u212A\u212B\u2329\u232A\u2ADC\uF900-\uFA0D\uFA10\uFA12\uFA15-\uFA1E\uFA20\uFA22\uFA25\uFA26\uFA2A-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4E]|\uD834[\uDD5E-\uDD64\uDDBB-\uDDC0]|\uD87E[\uDC00-\uDE1D]/

しかし、このパターンはあまりにも長いので、以降は以下のパターンを「合成除外の文字にマッチする正規表現」とします。
(なお、このパターンは文字集合に含まれる文字を変更しただけで、本来のパターンと構造は変わりません)

JavaScript

1/[a-z]|@[A-H]|#[1-9]/

そして、「合成除外の文字のみで構成された文字列」に対して置換処理を行う場合、以下の様なコードになります。

JavaScript

1str.replace(/([a-z]|@[A-H]|#[1-9])+/g, function(match){ 2 // 何かの処理 3 // 変数matchには「合成除外の文字のみで構成された文字列」が含まれている 4});

さて、この逆である、「合成除外以外の文字のみで構成された文字列」に対して置換処理を行う場合、どのような正規表現を書けば良いでしょうか?

JavaScript

1str.replace( ... , function(match){ 2 // 何かの処理 3 // 変数matchには「合成除外以外の文字のみで構成された文字列」が含まれている 4});

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

regenerateで作成してしまうのはどうでしょうか?あらかじめjogai変数に合成除外のコードポイントを配列として入れといて、全体からremoveすればいいのではないかと思います。

var regenerate = require("regenerate"); var jogai = [ 0x0958, 0x0959, 0x095A, 0x095B, // 中略 0x1D1BD, 0x1D1BE, 0x1D1BF, 0x1D1C0, ]; // これはサンプルです。 var nojogai = new RegExp("(" + regenerate().addRange(0, 0x10FFFF).remove(jogai).toString() + ")+", "g");

上のnojogaiを使ってmatchすれば、サロゲート文字でもうまくいくのではないでしょうか。

投稿2015/11/28 14:49

raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sounisi5011

2015/11/28 15:10

regenerate.prototype.removeというメソッドがあったんですね! ドキュメントをちゃんと読んでいたつもりでしたが、見落としていたようです。
sounisi5011

2015/11/28 15:18

最終的に、以下のコードで: '(?:' + regenerate() .addRange(0, 0x10FFFF) .remove(0x0340, 0x0341, 0x0343, 0x0344, 0x0374, 0x037E, 0x0387) .removeRange(0x0958, 0x095F) .remove(0x09DC, 0x09DD, 0x09DF, 0x0A33, 0x0A36) .removeRange(0x0A59, 0x0A5B) .remove(0x0A5E, 0x0B5C, 0x0B5D, 0x0F43, 0x0F4D, 0x0F52, 0x0F57, 0x0F5C, 0x0F69, 0x0F73, 0x0F75, 0x0F76, 0x0F78, 0x0F81, 0x0F93, 0x0F9D, 0x0FA2, 0x0FA7, 0x0FAC, 0x0FB9, 0x1F71, 0x1F73, 0x1F75, 0x1F77, 0x1F79, 0x1F7B, 0x1F7D, 0x1FBB, 0x1FBE, 0x1FC9, 0x1FCB, 0x1FD3, 0x1FDB, 0x1FE3, 0x1FEB, 0x1FEE, 0x1FEF, 0x1FF9, 0x1FFB, 0x1FFD, 0x2000, 0x2001, 0x2126, 0x212A, 0x212B, 0x2329, 0x232A, 0x2ADC) .removeRange(0xF900, 0xFA0D) .remove(0xFA10, 0xFA12) .removeRange(0xFA15, 0xFA1E) .remove(0xFA20, 0xFA22, 0xFA25, 0xFA26) .removeRange(0xFA2A, 0xFA6D) .removeRange(0xFA70, 0xFAD9) .remove(0xFB1D, 0xFB1F) .removeRange(0xFB2A, 0xFB36) .removeRange(0xFB38, 0xFB3C) .remove(0xFB3E, 0xFB40, 0xFB41, 0xFB43, 0xFB44) .removeRange(0xFB46, 0xFB4E) .removeRange(0x1D15E, 0x1D164) .removeRange(0x1D1BB, 0x1D1C0) .removeRange(0x2F800, 0x2FA1D) .toString() + ')+'; 以下のパターンを生成することに成功しました: (?:[\0-\u033F\u0342\u0345-\u0373\u0375-\u037D\u037F-\u0386\u0388-\u0957\u0960-\u09DB\u09DE\u09E0-\u0A32\u0A34\u0A35\u0A37-\u0A58\u0A5C\u0A5D\u0A5F-\u0B5B\u0B5E-\u0F42\u0F44-\u0F4C\u0F4E-\u0F51\u0F53-\u0F56\u0F58-\u0F5B\u0F5D-\u0F68\u0F6A-\u0F72\u0F74\u0F77\u0F79-\u0F80\u0F82-\u0F92\u0F94-\u0F9C\u0F9E-\u0FA1\u0FA3-\u0FA6\u0FA8-\u0FAB\u0FAD-\u0FB8\u0FBA-\u1F70\u1F72\u1F74\u1F76\u1F78\u1F7A\u1F7C\u1F7E-\u1FBA\u1FBC\u1FBD\u1FBF-\u1FC8\u1FCA\u1FCC-\u1FD2\u1FD4-\u1FDA\u1FDC-\u1FE2\u1FE4-\u1FEA\u1FEC\u1FED\u1FF0-\u1FF8\u1FFA\u1FFC\u1FFE\u1FFF\u2002-\u2125\u2127-\u2129\u212C-\u2328\u232B-\u2ADB\u2ADD-\uD7FF\uE000-\uF8FF\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29\uFA6E\uFA6F\uFADA-\uFB1C\uFB1E\uFB20-\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFB4F-\uFFFF]|[\uD800-\uD833\uD835-\uD87D\uD87F-\uDBFF][\uDC00-\uDFFF]|\uD834[\uDC00-\uDD5D\uDD65-\uDDBA\uDDC1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+ ただこのパターン、文字に対するマッチ部分のみを比較すると、元が614文字でこちらは971文字と、158%も増加しています。 別の回答で検討されている、否定の正規表現と比較してみます。
sounisi5011

2015/11/28 19:30

http://bl.ocks.org/sounisi5011/raw/91bbf9ec53385419065f/ Gistでパフォーマンス計測コードを作成し実測したところ、 殆どのブラウザで、本回答で提案されたregenerateを利用して作成したパターンが最速となりました。 唯一の例外はInternet Explorerで、バージョン9以降では逆に別解のパターンが最速となっています。 この結果をもって、ベストアンサーを確定させていただきます。
guest

0

"@H""#1" を消費する事で該当文字を読み飛ばせます。

JavaScript

1function sample (string) { 2 return string.replace(/[a-z]+|@[A-H]|#[1-9]|([^a-z#@]*(?:(?:@[^a-zA-H]|#[^a-z1-9])[^a-z#@]*)*)/g, function (string, capture1) { 3 return capture1 ? '<' + capture1 + '>' : string; 4 }) 5} 6 7console.log(sample('hogeFOO@F123#1PIYOfoo')); // hoge<FOO>@F<123>#1<PIYO>foo

(2015/11/29 02:30追記)
@HH にマッチする正規表現を書いたり、質問の前提が変わって回答内容が変化したり、と追記を繰り返して分かりづらくなってしまったので回答全体を修正しました。

Re: sounisi5011さん

投稿2015/11/28 14:22

編集2015/11/28 17:30
think49

総合スコア18164

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sounisi5011

2015/11/28 14:39

例えのパターンが不適切でした。 /[a-z]|@[0-9]|#[0-9]/だと、数字の文字集合 [0-9] の箇所が重複していますが、本来のパターンは重複していません。 /[a-z]|@[A-H]|#[1-9]/に変更したため、もしよろしければこれに沿った最適化パターンを提示していただけるとありがたいです。 なお、大文字小文字の区別はしないものとします。
think49

2015/11/28 15:00

回答を追記しました。
sounisi5011

2015/11/28 15:40 編集

> なお、大文字小文字の区別はしないものとします。 この箇所ですが、正しくは「大文字小文字は区別するものとします」でした。 [a-z]と[A-H]の混同を避けるつもりで入れましたが、誤っていました。 ただ、最適化のパターンでは私の懸念していた『[a-z]と[A-H]の混同』は発生していないようなので、杞憂だったようです。
sounisi5011

2015/11/28 16:10 編集

通常版の正規表現を、合成除外以外の文字にマッチする正規表現に書きなおしました: (?:(?![\u0340\u0341\u0343\u0344\u0374\u037E\u0387\u0958-\u095F\u09DC\u09DD\u09DF\u0A33\u0A36\u0A59-\u0A5B\u0A5E\u0B5C\u0B5D\u0F43\u0F4D\u0F52\u0F57\u0F5C\u0F69\u0F73\u0F75\u0F76\u0F78\u0F81\u0F93\u0F9D\u0FA2\u0FA7\u0FAC\u0FB9\u1F71\u1F73\u1F75\u1F77\u1F79\u1F7B\u1F7D\u1FBB\u1FBE\u1FC9\u1FCB\u1FD3\u1FDB\u1FE3\u1FEB\u1FEE\u1FEF\u1FF9\u1FFB\u1FFD\u2000\u2001\u2126\u212A\u212B\u2329\u232A\u2ADC\uF900-\uFA0D\uFA10\uFA12\uFA15-\uFA1E\uFA20\uFA22\uFA25\uFA26\uFA2A-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4E]|\uD834[\uDD5E-\uDD64\uDDBB-\uDDC0]|\uD87E[\uDC00-\uDE1D])[\s\S])+ パターン全体は629文字です。 ただし、[\s\S]はmフラグの追加により(フラグの1字を含めて)4文字分減らせるので、最短は625文字となります。 また、最適化版の正規表現を、合成除外以外の文字にマッチする正規表現に書きなおしました: [^\u0340\u0341\u0343\u0344\u0374\u037E\u0387\u0958-\u095F\u09DC\u09DD\u09DF\u0A33\u0A36\u0A59-\u0A5B\u0A5E\u0B5C\u0B5D\u0F43\u0F4D\u0F52\u0F57\u0F5C\u0F69\u0F73\u0F75\u0F76\u0F78\u0F81\u0F93\u0F9D\u0FA2\u0FA7\u0FAC\u0FB9\u1F71\u1F73\u1F75\u1F77\u1F79\u1F7B\u1F7D\u1FBB\u1FBE\u1FC9\u1FCB\u1FD3\u1FDB\u1FE3\u1FEB\u1FEE\u1FEF\u1FF9\u1FFB\u1FFD\u2000\u2001\u2126\u212A\u212B\u2329\u232A\u2ADC\uF900-\uFA0D\uFA10\uFA12\uFA15-\uFA1E\uFA20\uFA22\uFA25\uFA26\uFA2A-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4E\uD834\uD87E]*(?:(?!\uD834[\uDD5E-\uDD64\uDDBB-\uDDC0]|\uD87E[\uDC00-\uDE1D])[\uD834\uD87E][^\u0340\u0341\u0343\u0344\u0374\u037E\u0387\u0958-\u095F\u09DC\u09DD\u09DF\u0A33\u0A36\u0A59-\u0A5B\u0A5E\u0B5C\u0B5D\u0F43\u0F4D\u0F52\u0F57\u0F5C\u0F69\u0F73\u0F75\u0F76\u0F78\u0F81\u0F93\u0F9D\u0FA2\u0FA7\u0FAC\u0FB9\u1F71\u1F73\u1F75\u1F77\u1F79\u1F7B\u1F7D\u1FBB\u1FBE\u1FC9\u1FCB\u1FD3\u1FDB\u1FE3\u1FEB\u1FEE\u1FEF\u1FF9\u1FFB\u1FFD\u2000\u2001\u2126\u212A\u212B\u2329\u232A\u2ADC\uF900-\uFA0D\uFA10\uFA12\uFA15-\uFA1E\uFA20\uFA22\uFA25\uFA26\uFA2A-\uFA6D\uFA70-\uFAD9\uFB1D\uFB1F\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4E\uD834\uD87E]*)* このパターンは1221文字となりました。 別解にあった、regenerateを利用して作成したパターンは976文字なので、字数では通常版が勝っているようですが… パフォーマンス計測等を行ったうえで、ベストアンサーを出させていただきます。
think49

2015/11/28 17:07 編集

後で気が付いたのですが、通常版は "@H" で "H" にマッチする不具合が存在しますね。 該当の文字列以外という発想は止めて、該当文字列を消費してそれ以外の文字列にマッチしたほうが良さそうです。 /(?:(?![a-z]|@[A-H]|#[1-9])[\s\S])+/g.exec('hoge@H#01323foo'); // ["H#01323"]
think49

2015/11/28 17:30

親コメントに回答を修正して投稿しました。
sounisi5011

2015/11/28 19:29

http://bl.ocks.org/sounisi5011/raw/91bbf9ec53385419065f/ Gistでパフォーマンス計測コードを作成し実測したところ、 殆どのブラウザで、別解にあったregenerateを利用して作成したパターンが最速となりました。 唯一の例外はInternet Explorerで、バージョン9以降では逆に本回答のパターンが最速となっています。 この結果をもって、ベストアンサーを確定させていただきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問