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

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

ただいまの
回答率

89.10%

JavaScriptのreplaceで、連続したmatchを効かせたいです

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 188

mezamashiTV

score 6

実現したいこと

テキストのタグ #タグ としたいです。

この条件がございまして、実は全てクリアできています。
・前にシャープをつける
・後ろにスペースをつける
・前がpattern.match()しなければ前にスペースをつける
・先頭なら前のスペースなし、末尾なら後ろのスペースなし
・タグが連続したあ間のスペースは1つ
・元からタグならそのまま

問題

昨日から取り組んでいる内容で、教えて頂いたり改変したりでなんとか上の条件はクリアできました。

以下のようにstr
食パンあとジャムパンあとごまあんぱんのときは
#食パン あと #ジャムパン あと #ごまあんぱんとなり理想的なのですが、

しかしstr
食パンジャムパンごまあんぱんのときは
#食パン ジャムパン #ごまあんぱんとなってしまいます。

ジャムパンが抜けてしまうのです。

コード

こちらは昨日yambejp様から頂戴したコードについて、is_tag()escapeRegExp()という関数を加えたものになります。

実行して頂くとジャムパンが抜けてしまうことがご覧いただけるかと思います。

var ARR = [
  {name:'食パン',id:1},
  {name:'あんぱん',id:2},
  {name:'ごまあんぱん',id:3},
  {name:'食パン(はちみつ味)',id:4},
  {name:'ジャムパン',id:5}
];

var str = "食パンジャムパンごまあんぱん";
console.log( get(str) ); // ジャムパンが抜けてしまう

function get( str ){

  var pattern = ["","。","、","!","!"];

  var reg = new RegExp("(^|.)("+ARR.map(x=>escapeRegExp(x.name)).sort((x,y)=>x.length<y.length).join('|')+")(.|$)","g");

  var res = str.replace(

      reg,x=>( ( r=x.match(new RegExp(reg.source)) ) ) &&

        // 元からタグの場合はそのまま
        is_tag(r[1],r[3]) ? x :

        // タグ化する
        r[1]
        + ( pattern.indexOf(r[1])>=0 ? "" : " " )
        + "#"
        + r[2]
        + ( r[3]=="" ? "" : " " )
        + r[3]

  );

  return res;

  // 前がシャープ、後がスペース|patternならばタグ
  function is_tag( before, after ){
    var before_match = before.match(/^[##]$/);
    var after_match = after.match(/^\s$/) || after.match(pattern);
    return ( before_match && after_match ) ? true : false;
  }  
}

// 正規表現をエスケープ
function escapeRegExp(string) {
    return string.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
}

試したこと

ジャムパンが受け取れない原因をさぐるためにxを出力してみましたら、やはりこの結果がおかしかったです。

  var res = str.replace(
      reg,x=>( ( r=x.match(new RegExp(reg.source)) ) ) &&
        console.log('x=「'+x+'」') 
  );


以下の結果となり、ジャムパンが受け取れないのはやはりxがおかしいからだとわかりましたが…しかし、いつどこでジャムパンが消えてしまうのか、どうしてもわかりません。

x=「食パンジ」
x=「ンごまあんぱん」 


さらに以下でreg.sourcestrもみましたが、それらにはジャムパンがありました。いつxからジャムパンが消えてしまうのか、さっぱりです。

  var res = str.replace(
      reg,x=>( ( r=x.match(new RegExp(reg.source)) ) ) &&
        console.log('x=「'+x+'」') 
        +console.log('str=「'+str+'」') 
        +console.log('reg.source=「'+JSON.stringify(reg.source)+'」') 
  );


どうすればジャムパンについても実現できますでしょうか。

補足情報(FW/ツールのバージョンなど)

特にバージョン情報は関係ないかと思いますが、もし解決策にjQueryをお使いになる場合がございましたら、最新の3.4での方法でお願いできましたら幸いです。

何卒、宜しくお願いいたします。

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

乗りかかった船なので一応以下で

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/02 16:44

    えなななんですかこれ…好きに入力できるように作ってくださったんですか…信じられません(泣)本当にありがとうございます。
    連日貴重なお時間を割いて下さいまして心より感謝申し上げます。

    キャンセル

  • 2020/04/02 16:53

    重要なのは
    ・まずはマッチした文字を抜き出すこと
    ・次に前後の文字を拾っておくこと
    これができれば8割完成しています。

    たぶんもっと効率的な書き方があるのですが
    効率性をあげると拡張性が犠牲になるので
    落とし所が難しいですよね・・・

    あとなに仕様追加が必要なら適当に質問いれておいてください

    キャンセル

  • 2020/04/02 19:18

    なるほど。効率と拡張性で悩むケースなどかつてありませんでした(動いたからヨシな姿勢でした)が、今回その重要性を目の当たりにできるまたとない機会となりました。

    正規表現のエスケープにハイフンを追加してくださったり等こまかい点までご配慮頂きまして誠にありがとうございます。おかげ様でやっとこの作業から抜け出せました。>_ _)>

    キャンセル

+1

前提が共有できていないかもですが、これではダメなのですか?

"食パンあとジャムパンあとごまあんぱん".replace(/(食パン|あんぱん|ごまあんぱん|食パン(はちみつ味)|ジャムパン)/g," #$& ").trim();
//"#食パン あと #ジャムパン あと #ごまあんぱん"

"食パンジャムパンごまあんぱん";.replace(/(食パン|あんぱん|ごまあんぱん|食パン(はちみつ味)|ジャムパン)/g," #$& ").trim();
//"#食パン  #ジャムパン  #ごまあんぱん"

再挑戦を追記

s.replace(
  /(.?)(食パン|あんぱん|ごまあんぱん|食パン(はちみつ味)|ジャムパン)/g,
  (match,p1,p2) =>
    match === p2          ? ` #${match} ` : 
    p1.match(/[。、!!]/) ? `${p1}#${p2} ` :
    p1 !== "#"            ? `${p1} #${p2} ` :
                             match
).trim();

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/02 16:41

    結局仕様が増えてくると冗長に処理する方が早かったりするんですよねー

    キャンセル

  • 2020/04/02 16:47

    再挑戦もありがとうございます。replaceの戻り値であるp1などを使うアプローチはいいですね。
    今回もまたyambejp様が解決してくださいましたが、Lhankor_Mhy様のコードも勉強になりました。

    キャンセル

  • 2020/04/02 17:03

    回答するというよりも、パズル的に楽しませていただきました。


    >結局仕様が増えてくると冗長に処理する方が早かったりするんですよねー

    しかりしかり。

    キャンセル

0

ジャムパンが抜けてしまう

ARR の name に 半角の (,) が利用されているためです。
正規表現パターンにおいてグループ化するための機能を持ちますので、エスケープが必要になります。

let quotemeta = str => str.replace(/\W/g, '\\$&');

追記)
エスケープはできていましたね。申し訳ありません。

"(^|.)(" + パターン + ")(.|$)" のパターンに対して
食パンジャムパンごまあんぱん とパンの名称が連続するのが原因です。

以下のようにマッチしています。

  1. 食パンジャムパンごまあんぱん」を評価し ^+"食パン"+. にマッチ
    (ジャムパンのにかかるため、その次の  からマッチ処理されます)
  2. 「ャムパンごまあんぱん」を評価し .+"ごまあんぱん"+$ にマッチ

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/04/02 15:34

    ありがとうございます。ご指摘の半角かっこはの部分は以下escapeRegExp()でエスケープしていると思うのですが、

    ARR.map(x=>escapeRegExp(x.name))

    これでは不足なのでしょうか?

    キャンセル

  • 2020/04/02 16:27

    追記しました。原因についてのみ言及し、yambejp さんの codepen を参考にしてみてください。

    キャンセル

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

  • ただいまの回答率 89.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る