対応方法
実装1、実装2ともに、基本的な考え方は同じです。
次のパターン、それぞれの正規表現を準備します。
https://www.google.co.jp/
のみにマッチする正規表現
[Google](https://www.google.co.jp/)
のみにマッチする正規表現
考慮無しに正規表現を準備してしまうと、1の正規表現で2のパターンが一致してしまいます。
そのため、前方否定戻り読み「(?<!~)」という機能を使って、「(」や「"」で囲まれている場合は1に該当させないという条件を付け加えます。
また、テキストの一部を組み替える必要があるので、名前付きグループ化を行い、フォーマットを指定して置換を行うようにしています。
実装に関して
単体テスト形式で回答いたします。
なお、XSS等の考慮はしていないので、ご注意ください。
実装例1: 2段階で置換を行うパターン
cs
1[TestMethod]
2public void TestTeratail356904_1()
3{
4 var expressionUrl = "s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+";
5
6 // グループ化(置換でこの部分を抜き出すため)
7 var expressionUrlGroup = "(?<url>" + expressionUrl + ")";
8
9 // 正規表現 URLのみ: 正規表現の前方否定戻り読み「(?<!~)」でカッコやダブルクォーテーションがマッチしないようにする
10 var expressionUrlOnly = "(?<![\(\\"]\s*)" + expressionUrlGroup + "(?!\s*[\)\\"])";
11 // ^^^^^^^ カッコやダブルクォーテーション
12 // ^^^^ スペースを入れた場合の考慮
13
14 // 正規表現 マークダウン
15 var expressionMarkdown = "\[(?<text>[^\]]*)\]\(\s*" + expressionUrlGroup + "\s*\)";
16
17 // 正規表現 URLのみ
18 var regexUrlOnly = new Regex(expressionUrlOnly,
19 RegexOptions.IgnoreCase // 大文字小文字を区別しない
20 | RegexOptions.Compiled // コンパイルする(この正規表現を使いまわす場合)
21 );
22 // 正規表現 マークダウン
23 var regexMarkdown = new Regex(expressionMarkdown,
24 RegexOptions.IgnoreCase // 大文字小文字を区別しない
25 | RegexOptions.Compiled // コンパイルする(この正規表現を使いまわす場合)
26 );
27
28 // 置換元文字列
29 var sb = new StringBuilder();
30 sb.AppendLine("[Google](https://www.google.co.jp/)にアクセスしてください。");
31 sb.AppendLine("テラテイルはhttps://teratail.com/こちらです。");
32 var text = sb.ToString();
33
34 // 置換処理
35 text = regexUrlOnly.Replace(text, "<a href=\"${url}\">${url}</a>");
36 System.Diagnostics.Trace.TraceInformation("Url Only: {0}", text);
37 text = regexMarkdown.Replace(text, "<a href=\"${url}\">${text}</a>");
38 System.Diagnostics.Trace.TraceInformation("Markdown: {0}", text);
39
40 // テスト結果
41 var sbCorrect = new StringBuilder();
42 sbCorrect.AppendLine("<a href=\"https://www.google.co.jp/\">Google</a>にアクセスしてください。");
43 sbCorrect.AppendLine("テラテイルは<a href=\"https://teratail.com/\">https://teratail.com/</a>こちらです。");
44
45 Assert.AreEqual(sbCorrect.ToString(), text);
46}
47
実装例2: 1発で置換を行うパターン
cs
1[TestMethod]
2public void TestTeratail356904_2()
3{
4 var expressionUrl = "s?https?://[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]+";
5
6 // グループ化(置換でこの部分を抜き出すため)
7 var expressionUrlGroup = "(?<url>" + expressionUrl + ")";
8
9 // 正規表現 URLのみ: 正規表現の前方否定戻り読み「(?<!~)」でカッコやダブルクォーテーションがマッチしないようにする
10 var expressionUrlOnly = "(?<![\(\\"]\s*)" + expressionUrlGroup + "(?!\s*[\)\\"])";
11 // ^^^^^^^ カッコやダブルクォーテーション
12 // ^^^^ スペースを入れた場合の考慮
13
14 // 正規表現 マークダウン
15 var expressionMarkdown = "\[(?<text>[^\]]*)\]\(\s*" + expressionUrlGroup + "\s*\)";
16
17 // 正規表現 全て
18 var expressonAll = "(" + expressionUrlOnly + ")|(" + expressionMarkdown + ")";
19
20 var regexUrl = new Regex(expressonAll,
21 RegexOptions.IgnoreCase // 大文字小文字を区別しない
22 | RegexOptions.Compiled // コンパイルする(この正規表現を使いまわす場合)
23 );
24
25 // 置換元文字列
26 var sb = new StringBuilder();
27 sb.AppendLine("[Google](https://www.google.co.jp/)にアクセスしてください。");
28 sb.AppendLine("テラテイルはhttps://teratail.com/こちらです。");
29
30 // 置換処理
31 var text = regexUrl.Replace(sb.ToString(), (match) =>
32 {
33 var foundText = match.Groups["text"].Success;
34 return match.Result(foundText ? "<a href=\"${url}\">${text}</a>" : "<a href=\"${url}\">${url}</a>");
35 });
36
37 System.Diagnostics.Trace.TraceInformation("All: {0}", text);
38
39 // テスト結果
40 var sbCorrect = new StringBuilder();
41 sbCorrect.AppendLine("<a href=\"https://www.google.co.jp/\">Google</a>にアクセスしてください。");
42 sbCorrect.AppendLine("テラテイルは<a href=\"https://teratail.com/\">https://teratail.com/</a>こちらです。");
43
44 Assert.AreEqual(sbCorrect.ToString(), text);
45}
46
参考文献
本回答に辿り着く際に参照したリソース。