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

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

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

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

正規表現

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

Q&A

2回答

2294閲覧

【JavaScript】部分一致のある複数のキーワードをそれぞれ置換する方法について

nekoru_t

総合スコア10

JavaScript

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

正規表現

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

0グッド

2クリップ

投稿2019/02/28 23:51

前提・実現したいこと

お世話になります。
皆様のお知恵をお借りしたく質問させて頂きます。

テキストボックスに入力されたテキストから、ある特定のキーワード(単語やフレーズ)を見つけ出し、それらを<span></span>で囲んだ状態で、ブラウザに出力したい。
該当キーワードがテキスト内に無ければ、何もせずそのままブラウザに出力したい。

・テキストデータは英文
・見つけたいキーワードも英語で複数ある
・キーワードは部分一致するものがある
・キーワードは固定ではなく、定期的に追加される
・キーワードの管理は昇順・降順ではなく、登録順(日付順)

発生している問題・エラーメッセージ

置換したいキーワードの一部が他のキーワードと重複している場合、置換後の文字列が意図しない結果になる。

該当のソースコード

html

1<!DOCTYPE HTML> 2<html> 3 <head> 4 <meta charset="utf-8"> 5 <title>test</title> 6 <style> 7 html, body { 8 width: 100%; 9 height: 100%; 10 } 11 body { 12 display: flex; 13 justify-content: center; 14 } 15 #before, #after { 16 width: 800px; 17 font-size: 13px; 18 line-height: 1.4; 19 margin: 10px auto; 20 height: 200px; 21 } 22 span { 23 color: #ff0000; 24 } 25 </style> 26 <script type="text/javascript"> 27 /* キーワードを赤文字に変換 */ 28 function henkan() { 29 30 /* キーワード(例) */ 31 var array1 = [ 32 /white/g, 33 /apple/g, 34 /brown/g, 35 /tomato/g, 36 /cheese/g, 37 /Cheese/g, 38 /Cheese Inc./g, 39 /* 追加日 */ 40 /Cheese Cake/g, 41 /blueberry/g, 42 /blueberry pie/g, 43 /brown 2/g, 44 /yellow/g, 45 /brown4/g, 46 /* 追加日 */ 47 /cat/g, 48 /compact/g, 49 /water/g, 50 /water server/g, 51 /yellow®/g 52 ] 53 54 var beforeStr = document.querySelector('#before').value; 55 56 for (var i=0, len=array1.length; i<len; i++) { 57 beforeStr = beforeStr.replace(array1[i], '<span>$&</span>'); 58 } 59 60 beforeStr = beforeStr.replace(/\r\n|\n|\r/g, '<br>'); 61 62 document.querySelector('#after').innerHTML = beforeStr; 63 64 } 65 66 function myReset() { 67 var after = document.querySelector('#after'); 68 after.innerHTML = ""; 69 } 70 71 </script> 72 </head> 73 74 <body> 75 <form onReset="myReset()"> 76 <textarea id="before"></textarea> 77 <p> 78 <input type="button" id="btn" value="変換" onClick="henkan()"> 79 <input type="reset" value="リセット"> 80 </p> 81 <div id="after"></div> 82 </form> 83 </body> 84</html>

試したこと

単語の終わりや半角スペースに該当する記述を追加してみたが、改善せず。
/Cheese Cake/g → /Cheese\sCake\b/g

/Cheese/gより先に/Cheese Cake/gを記述したらCheese Cakeも<span></span>で囲まれたが、キーワードは日付順で登録・管理しているため勝手に動かせず元に戻した。

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

開発環境:Brackets
実行環境:Google Chrome最新版

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

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

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

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

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

guest

回答2

0

Cheese Cake<span><span>Cheese</span> Cake</span>になるので良ければ、
ループの前で、array1 = array1.sort((a,b)=>b.toString().length-a.toString().length);すればいいです。

あと、単語の前後に、単語境界指定を入れて/\bwhite\b/g とかにした方が良いかと思います。さらに、名詞は/\bapples?\b/gのように複数形も考慮した方が良いかも。

もう少し改善すると、複数形をさておくと、

JavaScript

1 let array1 = [ 2 "white", 3 "apple", 4 "brown", 5 "tomato", 6 "cheese", 7 "Cheese", 8 "Cheese Inc\.", 9 /* 追加日 */ 10 "Cheese Cake", 11 "blueberry", 12 "blueberry pie", 13 "brown 2", 14 "yellow", 15 "brown4", 16 /* 追加日 */ 17 "cat", 18 "compact", 19 "water", 20 "water server", 21 "yellowR" 22 ] 23 let regstr = "\b"+array1.sort((a,b)=>b.length-a.length).join("\b|\b")+"\b"; 24 let beforeStr = document.querySelector('#before').value; 25 beforeStr = beforeStr.replace(new RegExp(regstr), '<span>$&</span>'); 26 beforeStr = beforeStr.replace(/\r\n|\n|\r/g, '<br>');

質問文のコードのようにループで何回も置換せず、このように一回だけ置換すれば、
<span><span>Cheese</span> Cake</span>
みたいなことは起こりません。

投稿2019/03/01 00:16

編集2019/03/01 01:44
otn

総合スコア84499

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

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

0

・キーワードの管理は昇順・降順ではなく、登録順(日付順)
キーワードは日付順で登録・管理しているため勝手に動かせず元に戻した。

昇順降順にしたところで正常に動作するものではありませんが、ナンセンスですね。
コードに/* 2019.03.01 */追加って書いて、誰の何の利益が守られるんですか?その部分が事後的にいじれられてないことはバージョン管理ツールしか保証できないでしょう。バージョン管理してるなら無駄ですよね。

/Cheese/gより先に/Cheese Cake/gを記述したらCheese Cakeも<span></span>で囲まれたが

他の回答でも言及されていますが<span><span>cheese</span> cake</span>が顧客が求める結果なのでしょうか?

この問題は基本的に単語の個数によって先後を決めなければ妥当な動きをしない構造をしています。
例えば、全てを1フレーズと思って、各フレーズの文字列を辞書順昇順で並べられている場合でも
"cheese" < "cheese cake"となりcheese cakeにマッチしなくなり、かといって辞書順降順で並べたとしても
"chiken" > "cheese chicken" > "cheese"のように複数語からなる後半の単語の辞書出現順によって正しく並びません。
さらに手をかけて1単語目を昇順、1単語目が同じ場合は降順、としたところで3単語以上になったとき("cheese chicken burger"など)1単語目が昇順であるべきかどうか、1単語目がおなじものどうしで昇順であるべきかどうかを、ロジックとして決められません。
各単語の長さにもよりますので、全体の長さからどうソートすれば正しく単語がspanで囲めるは決められません同じ単語で構成される以上、複合語は単一単語より長くなるため、全体長で長いほうからソートすれば自己衝突は避けられました。失礼しました。

同じ日に追加しているcheeseとcheese incやblueberry, blueberry pieさえ正常に動作する順序になっていないのですから、複合語のことを全く考えてもいないロジックで最初に入れたセットと思しきなかでもcheese incは正常に動いてないので、リセットしてゼロから考え直さないといけないレベルのものです。

  • 対応策

まず単語数によって分ける(どうしても日付管理したいなら所与の文字の配列を、プログラムで分けてもいいです)。
そのうえで、単語数が多いものから処理する
(さらに置換時に、タグの中にいないことを検証すれば<span><span></span> </span>を避けられる)

  • 対応しない策

現在のプログラムでcheese cakeだけ対応したことにしたいなら、/cheese(</span>)? cake/のように閉じタグがあっても許可する正規表現にすればいいだけです。

投稿2019/03/01 01:33

編集2019/03/01 01:43
papinianus

総合スコア12705

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問