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

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

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

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

正規表現

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

Q&A

7回答

1744閲覧

「:の後でない文字列」を探す正規表現 (JS)

elpha

総合スコア18

JavaScript

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

正規表現

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

0グッド

1クリップ

投稿2018/11/29 17:53

編集2022/01/12 10:55

HTML

1<div> 2 <section> 3 <span style="color:#f90">文字列</span><br> 4 <span style="color:#f90">文字列</span><br> 5 <span style="color:#f90">文字列</span><br> 6 #ハッシュタグ 7 #ハッシュタグ #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 8 #ハッシュタグ 9 <a href="http://example.jp/#top">#ハッシュタグ 10 </section> 11</div>

(HTMLは簡略化した例であり、実際はもっと入り組んでいます)

jQuery

1const replaced = $('div').html().replace(/(?!(:#...">))([##].+)([\s<]+)/g, ""); 2$('div').html( replaced );

(ひとまず簡略化のため単純に消去 実際はaタグで囲うなどしたい)

というようなイメージで、カラーコード部分(およびリンクのURL部分)以外の「#で始まる文字列」を置換したいのですが
どうもカラーコード部分も置換されてしまいます

(自分の作った文書ではなく依頼されたものであるため、色をrgb(255,…)に書き換えたりcssで色をつけたりはできません)

先にカラーコード部分を

jQuery

1const replaced = $('div').html().replace(/color:#/g, "color:"); 2$('div').html( replaced );

と置換して"color:f90"という形式にしておき、後で戻す(リンクのURLも同様に) …という方法は思い浮かびますが
もしかして、そうするしかないでしょうか?

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

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

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

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

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

jun68ykt

2018/12/01 04:17 編集

気になったのは、 # の前に : や / がある文字列(例: ":#f90", "/#top" など)が、styleやhrefなどの属性値ではなく、テキストノードに含まれている場合、これらをreplace の対象にしたいのかどうかという点です。(ちなみに私の回答では、テキストノードに含まれる場合は replace の対象になるようにしています。)
think49

2018/12/01 09:22

掲示されたHTMLで </a> が抜けているので修正して下さい
guest

回答7

0

カラーコードに誤爆する

まず、要件に問題がないか確認して下さい。
カラーコードに誤爆する為にカラーコードにマッチしない正規表現にすることを考えたようですが、そうすると次の問題があります。

  • #bad などの正しいハッシュタグにもマッチしなくなる
  • <span title="#foo"><a href="http://example.jp/#top"> にはマッチしてしまう

属性値にマッチさせない

問題は属性値にマッチすることなので、カラーコード云々は無視して良いでしょう。

HTML

1<div> 2 <section> 3 <span style="color:#f90">文字列</span><br> 4 <span style="color:#f90">文字列</span><br> 5 <span style="color:#f90">文字列</span><br> 6 #ハッシュタグ 7 #ハッシュタグ #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 8 #ハッシュタグ 9 <a href="http://example.jp/#top">#top</a> 10 </section> 11</div> 12<script> 13'use strict'; 14const htmlString = jQuery('div>section').html().replace(/^[^<>]+(?=<)|>[^<>]+(?=<|$)/g, function (match) { 15 return match.replace(/#[^\s<>]+/g, ''); 16}); 17console.log(htmlString); 18</script>

<a> はネスト出来ない

そして、空文字に置換がサンプル故の省略でハッシュタグにリンクを貼る事が目的であった場合、更なる対策が必要となるでしょう。

HTML

1<a href="https://twitter.com/hashtag/javascript">#javascript</a>

a要素は構造上、入れ子に出来ませんので、

HTML

1<a href="https://twitter.com/hashtag/javascript"><a href="https://twitter.com/hashtag/javascript">#javascript</a></a>

このような置換が発生する事は期待に反します。
従って、「a要素の子孫ではないテキストノード値」という条件が必要となります。

a要素の子ノードにマッチさせない

下記はa要素の子ノードではない場合限定の正規表現になります。

JavaScript

1const htmlString = jQuery('div>section').html().replace(/^[^<>]+(?=<(?!/a>))|>[^<>]+(?=<(?!/a>)|$)/g, function (match) { 2 return match.replace(/(#[^\s<>]+)/g, '<a href="https://twitter.com/hashtag/javascript">$1</a>'); 3}); 4console.log(htmlString);

a要素の子孫ノードにマッチさせない

(2018/12/01 20:54追記)
a要素の子孫ノードにマッチさせない場合は、次の正規表現を使います。

HTML

1<div> 2 <section> 3 <span style="color:#f90">文字列</span><br> 4 <span style="color:#f90">文字列</span><br> 5 <span style="color:#f90">文字列</span><br> 6 #ハッシュタグ 7 #ハッシュタグ #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 8 #ハッシュタグ 9 <a href="http://example.jp/#top">#top</a> 10 <a href="http://example.jp/#foo"><span>#foo</span></a> 11 </section> 12</div> 13<script> 14'use strict'; 15const htmlString = jQuery('div>section').html(function (i, htmlString) { 16 return htmlString.replace(/^[^<>]+(?=<)|>[^<>]+(?=<|$)(?!(?:</(?!a>)[^>]*>[^<]*)*</a>)/g, function (match) { 17 return match.replace(/(#[^\s<>]+)/g, '<a href="https://twitter.com/hashtag/javascript">$1</a>'); 18 }); 19}).html(); 20 21console.log(htmlString); 22</script>

DOM APIでテキストノードを置換する

正規表現でHTMLタグを置換すると、DOMノードに割り当てられたイベント等の参照系機能の全てが解除されます。
DOM APIでテキストノードを置換すれば、この影響を最小限に抑えることが出来ます。

JavaScript

1'use strict'; 2const xpathResult = document.evaluate('descendant::div/section/descendant-or-self::text()', document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 3 4for (let i = 0, length = xpathResult.snapshotLength; i < length; ++i) { 5 const textNode = xpathResult.snapshotItem(i); 6 let targetNode = textNode; 7 8 while (targetNode = targetNode.parentNode && targetNode.tagName !== 'A'); 9 10 if (!targetNode) { 11 const df = document.createDocumentFragment(), reg = /([^#]+)|#[^\s#]+/g, text = textNode.data, a = document.createElement('a'); 12 let result; 13 a.appendChild(document.createTextNode('#')); 14 15 while (result = reg.exec(text)) { 16 if (result[1]) { 17 df.appendChild(document.createTextNode(result[1])); 18 } else { 19 const hash = result[0]; 20 21 a.href = 'https://twitter.com/hashtag/' + hash; 22 a.firstChild.data = hash; 23 df.appendChild(a.cloneNode(true)); 24 } 25 } 26 27 if (!result) { 28 textNode.parentNode.replaceChild(df, textNode); 29 } 30 } 31}

Re: elpha さん

投稿2018/12/01 10:51

編集2018/12/01 12:13
think49

総合スコア18156

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

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

think49

2018/12/01 11:40

「a要素の子孫ノードではない正規表現」が期待通りの動作ではなかった為、子ノード限定の正規表現に修正しました。
think49

2018/12/01 11:58

子孫ノードにも対応できた為、追記しておきました。 書いておいてなんですが、正規表現で対応するのは、相当頭をひねる必要があるのでお勧めはしません。 短いコードでエレガントに見えるかもしれませんが、内容を理解して使わないと何かあった時に自分で対処できなくなります。 DOMでノードを置換する方が素直なコードで理解しやすいですし、イベントも剥がされないのでお勧めです。
colling

2018/12/01 11:59

なるほど、質問文をちゃんと読めば a要素の入れ子 の可能性がありますね。さすがです。
guest

0

カラーコードは#12b#12abefのように基本的に3桁か6桁の16進数で出来ています。
※最新のブラウザは4桁、8桁にも対応しており、透明度込みのカラーコードと判断します
https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#rgba

なので.のようなワイルドカードで指定しまうのではなく、[0-9a-fA-F]のような16進数の数値として妥当な文字列だけ受け付けることで一歩前進します。
また、+という風に無限長の文字列を受け付けるのではなく、{3,8}で指定すれば、
質問文のような回避策を使わなくてもある程度行けるんじゃないですかね?
(5,7桁のような不正なコードも受理されるのでもう少し改良したい所ではありますが)

まずはどのような文字列が来るかを意識して組んでみてください。

投稿2018/11/30 02:06

編集2018/12/05 03:47
miyabi-sun

総合スコア21158

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

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

s8_chu

2018/12/05 03:17 編集

あまり問題と関係のない、細かいところですが、念のために。 > 基本的に3桁か6桁の16進数 最近のブラウザでは #RRGGBBAA も動きます。
guest

0

こんにちは

この回答は、ご質問のタイトルである 「:の後でない文字列」を探す正規表現 という趣旨からは外れたものになっておりますので、参考程度に留めて頂ければと思います。

ご質問の文中に

(…中略…)というようなイメージで、カラーコード部分(およびリンクのURL部分)以外の「#で始まる文字列」を置換したいのですが

とあり、特に上記の中に

およびリンクのURL部分

とあったことから、質問者さまが実現したいことを掘り下げると、

  • 与えられたHTMLに含まれる、 # で始まる文字列に対して、replaceなどの何らかの操作を行いたいが、この操作はテキストノードに含まれる文字列のみを対象とし、要素の(styleやhrefなどの)属性値に該当の文字列が含まれていても操作の対象外としたい。

ということであると推察しました。(これが間違っているようでしたら、以下の回答は、elpha さんにとって価値のないものと思われますので、無視してください。)

上記の解釈に基づいて、JavaScriptで与件を実現するコードを書こうとすると、まずはテキストノードだけを抽出するコードの実装から考え始めることになりますが、これは正規表現で頑張らずとも、DOM(Document Object Model)を操作するために用意されている豊富なAPIを使った方が楽にできます。(とはいえ、テキストノードだけを対象にして、最終的に行いたい変換まで、何から何まで全部をやってくれる、いわば一発必中の正規表現を書いてやろうという、果敢な挑戦が悪いと言っているわけではありません。)

以下は、テキストノードだけを抽出するコードの一例です。

html

1<div id="outer"> 2 <section> 3 <span style="color:#f90">文字列</span><br> 4 <span style="color:#f90">文字列</span><br> 5 <span style="color:#f90">文字列</span><br> 6 #ハッシュタグ1 7 #ハッシュタグ2 #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 8 #ハッシュタグ3 9 <a href="http://example.jp/#top">#ハッシュタグ4</a> 10 </section> 11</div>

javascript

1function getTextNodes(root) { 2 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); 3 const textNodes = []; 4 let node; 5 while(node = walker.nextNode()) { 6 if (node.nodeValue.trim().length > 0) { 7 textNodes.push(node); 8 } 9 } 10 return textNodes; 11} 12 13const textNodes = getTextNodes(document.getElementById('outer'));

上記では、 createTreeWalker を使ってテキストノードを収集して配列として返す関数getTextNodes(root) を作り、これの返り値を textNodesという変数に入れています。

その後、テキストノードの配列textNodes の各要素に対して、何らかの処理を行います。以下はその一例です。

textNodes.forEach(t => { t.nodeValue = t.nodeValue.replace(/#([^#\s]+)/g, "{$1}") ; });

上記では、テキストノードに含まれる #ハッシュタグ という文字列を、 {ハッシュタグ} にreplace しています。ここまでを動作確認するサンプルを以下に作成しました。   

上記のコードでは、 # の前に :/ がある文字列、例えば :#ハッシュタグ/#ハッシュタグ といった文字列も、これらがテキストノードに出現するのであれば、変換の対象になります。以下はそのサンプルです。

追記(#タグ をリンクに変換するサンプル)

参考までに、上記の回答に挙げたコードを発展させて、#タグ をリンクに変換するサンプルを作ってみました。以下です。

上記のindex.htmlを表示すると、以下のようになります。

イメージ説明

「タグをリンクにする」ボタンをクリックすると以下のように、 #タグ にリンクが付加されます。

イメージ説明

なお、動作確認のため、各タグのリンク先を Wikipedia の該当ページにしています。また、初期表示の状態から、<a> の innerText に含まれるタグ #161170 はリンク化の対象外にしています。

投稿2018/12/01 02:32

編集2018/12/02 12:40
jun68ykt

総合スコア9058

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

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

kei344

2018/12/01 05:24

node.nodeValue に代入しているので「aタグで囲うなどしたい」は(そのままでは)できないような。
jun68ykt

2018/12/01 12:47

@kei344さん はい。「aタグで囲うなどしたい」場合は、あともうひと手間、かかりそうです。
guest

0

「入り組んでいる」が具体的にどうか、という話で、挙げた例なら、こうやればできる
(ただし、#ハッシュTAG#hashタグを一つのハッシュタグと認知しているので気をつけてください)

javascript

1 const html = `<div> 2 <section> 3 <span style="color:#f90">文字列</span><br> 4 <span style="color:#f90">文字列</span><br> 5 <span style="color:#f90">文字列</span><br> 6 #ハッシュタグ 7 #ハッシュタグ #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 8 #ハッシュタグ 9 <a href="http://example.jp/#top">#ハッシュタグ 10 </section> 11</div>`; 12 const conv = html.replace(/([^:/])[##].*?(?=[\s<])/g, "$1"); 13 console.log(conv); 14/* console 15<div> 16 <section> 17 <span style="color:#f90">文字列</span><br> 18 <span style="color:#f90">文字列</span><br> 19 <span style="color:#f90">文字列</span><br> 20 21 <span style="color:#f90">文字列</span> 22 23 <a href="http://example.jp/#top"> 24 </section> 25</div> 26*/

あとは、replaceの第二引数には関数を渡せるから、長めにマッチさせて#の手前の文字列を見たり、#からはじまる部分がカラーコードっぽいか(っていってもa hrefの#も弾きたいので多分無理だな)みたいは判定をかませることはできると思う。

投稿2018/11/30 02:20

papinianus

総合スコア12705

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

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

0

否定後読み言明というのが使えると簡単なのですが、残念ながらjavascriptでは使えないようです。
そこで、カラーコードとそれ以外とで文字列を分割して処理するのはどうでしょう。

a = '<span style="color:#900">#123</span><br>'; a_list = a.split(/(#...)/);

こうするとa_listの1,3,5...番目にカラーコード、0,2,4,...番目にその間の文字列が入っているはずです。偶数番目の文字列をそれぞれチェックして、末尾が:ならその直後の要素に大して置換処理を行い、最後にリストをくっつけ直して終了。

投稿2018/11/29 19:50

KojiDoi

総合スコア13669

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

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

think49

2018/12/01 12:49 編集

質問者の意図を反映したコードは /(?<!color\s*:\s*)#[^#\s]+/.test('<span style="color:#900">') でしょうか。 (2018/12/01 21:49追記: 正規表現が誤っていたので修正しました) ただ、カラーコードが現われる場所は color プロパティに限定されないので、完全に対応しようとするとCSSパーサを書く労力を支払う事になる可能性があります。 例えば、background プロパティで一括指定されると、":" に後続してカラーコードが現われないので、正規表現が複雑化します。
guest

0

誰かもいってましたがやはり__否定後読み__でしょう
ES2018での追加で、まだ最新のChromeでしか検証できないですが・・・
もしくはそれを基準にした考え方で代替案としては[こちら](「代替え案」 https://qiita.com/knagisa/items/a37520974e39b25b7a6f)が参考になると思います

HTML

1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta http-equiv="X-UA-Compatible" content="ie=edge"> 7 <title>Document</title> 8</head> 9<body> 10 <div class="target"> 11 <section> 12 <span style="color:#f90">文字列</span><br> 13 <span style="color:#f90">文字列</span><br> 14 <span style="color:#f90">文字列</span><br> 15 #ハッシュタグ 16 #ハッシュタグ #ハッシュTAG#hashタグ<span style="color:#f90">文字列</span> 17 #ハッシュタグ 18 <a href="http://example.jp/#top">#ハッシュタグ</a> 19 </section> 20 </div> 21 <div id='btn'>Click</div> 22<script src="./jquery.min.js"></script> 23<script> 24$(document).ready(function(){ 25 $('#btn').click(function(){ 26 const replaced = $('div.target').html().replace(/(?<!color:) [##].+/g, ""); 27 $('div.target').html(''); 28 $('div.target').html( replaced ); 29 }); 30}); 31 32</script> 33</body> 34</html>

投稿2018/12/05 13:28

Fujiman

総合スコア41

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

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

0

どのような文字列が来るかを想像しながら、正規表現を見直してみましょう。

カラーコード部分(およびリンクのURL部分)以外の「#で始まる文字列」を置換したいのですが
どうもカラーコード部分も置換されてしまいます

ということなので"を弾くことで、#カラーコードも、aタグの中の#もすっ飛ばせます。

color:/#topに着目するのではなく
color:#f90"<-の"
/#top"<-の" に着目すれば良いのではというアイデアです。

--追記--
ここで、着目を htmlの閉じタグ>にしない理由は、htmlタグには他の属性が入ってくる可能性があるからです。
なので htmlタグの中に記載された#にはそのあと必ず"があるだろうという判断です。


!に置き換えしてみました。

#か#で始まって"を含まず、直後に <#がある文字列

javascript

1const replaced = $('div').html().replace(/[##][^\"]*?(?=[\s<##])/g, '!'); 2$('div').html( replaced );

aタグで囲いたい場合は、match()で上記の正規表現を使って取り出して、前後に文字列を足してあげれば良いのでは?

javascript

1const matched = $('div').html().match(/[##][^\"]*?(?=[\s<##])/g); 2console.log(matched);

--追記--
コメントで指摘のありました"... style="color:#aaa; background-color:#bbb;"について、一応頑張ってみました

javascript

1const matched = $('div').html().match(/[##][^\";]*?(?=[\s<##])/g); 2console.log(matched);

ですが、他の回答者さんから<a> はネスト出来ないという意見もありますので、正規表現1発では、あとあと落とし穴に引っかかりそうですね、、。

投稿2018/11/30 15:22

編集2018/12/02 12:41
colling

総合スコア798

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

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

KojiDoi

2018/11/30 15:54

本案件で解決すべき問題との関係がさっぱりわかりません。
colling

2018/11/30 16:03

#ハッシュタグのところの文字は任意ということでしょうか?
colling

2018/11/30 16:42

>KojiDoiさん htmlタグの中に存在する #から始まる文字列は除外したいってことですよね? それなら " で除外しちゃった方が良いと思いません?
colling

2018/12/01 00:43

KojiDoiさん にもわかりやすいように回答に解説を加えるとともに、こちらの勘違い部分(文字列の終わり)を修正しておきました。
KojiDoi

2018/12/02 10:32

>それなら " で除外しちゃった方が良いと思いません? おもいませんね。"... style="color:#aaa; background-color:#bbb;"のようなのもあり得ます。要件にない仮定を勝手に置く形になっています。
colling

2018/12/02 12:35

>KojiDoiさん あ〜なるほど。そこには全く気づいていませんでした! 他の回答者さんが気づいておられる、aタグが入れ子になるのでは?という点も含めて正規表現だけでは無理っぽいですね、、。 一応頑張ってみたという修正も上げておきます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

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

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

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問