-
Notifications
You must be signed in to change notification settings - Fork 0
/
BregonigRegex.cs
733 lines (681 loc) · 38.8 KB
/
BregonigRegex.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
namespace Hnx8.BregonigDotNet
{
/// <summary>
/// bregonig.dllのUnicodeAPIを使用して鬼雲による正規表現検索を行うクラスです。
/// </summary>
public class BregonigRegex : IDisposable
{
///////////////////////////////////////////////////////////////////////
//
// <BregonigRegex.cs>
// 2018.10.01~ written by hnx8(H.Takahashi)
// https://github.com/hnx8/BregonigDotNet
//
// C#からbregonig.dllの鬼雲による正規表現を利用するためのクラスです。
// http://k-takata.o.oo7.jp/mysoft/bregonig.html にて配布されている
// bregonig.dll(Ver.3以降)を実行フォルダに配置して下さい。
//
// 利用可能な正規表現・オプション指定はbregonig.dllの仕様通りです。
// 検索パターン http://k-takata.o.oo7.jp/mysoft/bregonig.html#SCT-5.1
// 置換パターン http://k-takata.o.oo7.jp/mysoft/bregonig.html#SCT-5.3
// オプション http://k-takata.o.oo7.jp/mysoft/bregonig.html#SCT-4.3.1-OPTIONS
// を参照してください。
// Unicode版APIを呼び出します。ANSI用オプションは使用できません。
//
///////////////////////////////////////////////////////////////////////
#region innerclass ----------------------------------------------------
/// <summary>
/// 正規表現にマッチした結果を表すクラスです。
/// </summary>
public class Matched
{
/// <summary>
/// 正規表現にマッチした部分文字列の開始位置を返します。
/// </summary>
public int Index { get { return Groups[0].Index; } }
/// <summary>
/// 正規表現にマッチした部分文字列の長さを返します。
/// </summary>
public int Length { get { return Groups[0].Length; } }
/// <summary>
/// 正規表現にマッチした部分文字列を返します。
/// </summary>
public string Value { get { return Groups[0].Value; } }
/// <summary>
/// 正規表現マッチ箇所についてのキャプチャされたグループの配列を返します。
/// [0]=マッチ箇所全体、[1]~[n]=グループ($1-$n)です。
/// キャプチャされていないグループに対応する添字のオブジェクトはnullとなります。
/// </summary>
public ReadOnlyCollection<Group> Groups { get; protected set; }
protected BregonigRegex regex; // grep実行時に指定されていた正規表現
protected string input; // grep実行時に指定されていた検索テキスト
protected int targetstart; // grep実行時に指定されていた検索開始文字位置
protected int targetend; // grep実行時に指定されていた検索終了文字位置
/// <summary>
/// 新しいインスタンスを初期化します。(BregonigRegex内部用、独自に作成したコードから直接使用するものではありません)
/// </summary>
internal Matched(Group[] groups, BregonigRegex regex, int targetstart, int targetend)
{
this.Groups = new ReadOnlyCollection<Group>(groups);
this.regex = regex;
this.input = regex.input;
this.targetstart = targetstart;
this.targetend = targetend;
}
/// <summary>正規表現にマッチした部分文字列を返します。</summary>
/// <returns></returns>
public override string ToString() { return Value; }
/// <summary>
/// 一致する対象が最後に見つかった位置の終了位置から開始して、次に一致する対象を検索します。
/// </summary>
/// <returns>検索によって見つかった Matched オブジェクト。見つからなかった場合はnull</returns>
/// <remarks>
/// BregonigRegex正規表現オブジェクトがすでにDispose済の場合は、ObjectDisposedExceptionが投げられます。
/// </remarks>
public Matched NextMatch()
{
// このマッチ文字列以降の位置を探索対象とする。ただし長さ0のマッチだった場合は無限ループ回避のため次の位置
int nextBeginning = Index + (Length == 0 ? 1 : Length);
if (nextBeginning > targetend) { return null; }
return regex.BoMatch(null, null, input, nextBeginning, targetend, false);
}
/// <summary>
/// 正規表現にマッチした部分文字列を、指定した置換パターンで置換して返します。
/// </summary>
/// <param name="replacement">使用する置換パターン</param>
/// <returns>置換パターン適用後の文字列</returns>
/// <remarks>
/// BregonigRegex正規表現オブジェクトがすでにDispose済の場合は、ObjectDisposedExceptionが投げられます。
/// 置換パターンにエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// </remarks>
public string Result(string replacement)
{
// このマッチ箇所以外は一括置換されないよう"g"オプションを外してBoSubst()を呼び出す
return regex.BoSubst(replacement,
(regex.options.Contains("g") ? regex.options.Replace("g", "") : regex.options),
this.input,
this.targetstart,
this.targetend,
this); // 置換された部分のみを返す
}
}
/// <summary>
/// 正規表現に一致したグループを表すクラスです。
/// </summary>
public class Group
{
/// <summary>キャプチャした部分文字列の開始位置を返します。</summary>
public readonly int Index;
/// <summary>キャプチャした部分文字列の長さを返します。</summary>
public readonly int Length;
/// <summary>キャプチャした部分文字列を返します。</summary>
public string Value { get { return input.Substring(Index, Length); } }
protected readonly string input; // grep実行時に指定されていた検索テキスト
/// <summary>
/// 新しいインスタンスを初期化します。(BregonigRegex内部用、独自に作成したコードから直接使用するものではありません)
/// </summary>
internal Group(string input, int index, int length)
{
this.input = input;
Index = index;
Length = length;
}
/// <summary>キャプチャした部分文字列を返します。</summary>
public override string ToString() { return Value; }
}
#endregion
#region static method -------------------------------------------------
/// <summary>
/// 指定した入力文字列内で、指定した正規表現パターンと一致する箇所を検索します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="options">正規表現オプション</param>
/// <returns>検索によって見つかった Matched オブジェクト。見つからなかった場合はnull</returns>
/// <remarks>
/// 正規表現にエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public static Matched Match(string input, string pattern, string options = null)
{
using (BregonigRegex regex = new BregonigRegex(pattern, options))
{
return regex.Match(input);
}
}
/// <summary>
/// 指定した入力文字列内で、指定した正規表現パターンと一致する箇所を検索しすべて列挙します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="options">正規表現オプション</param>
/// <returns>検索によって見つかった Matched オブジェクトの列挙</returns>
/// <remarks>
/// 正規表現にエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public static IEnumerable<Matched> Matches(string input, string pattern, string options = null)
{
using (BregonigRegex regex = new BregonigRegex(pattern, options))
{
return regex.Matches(input);
}
}
/// <summary>
/// 指定した入力文字列内で、指定した正規表現パターンと一致する箇所が見つかるかどうかを返します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="options">正規表現オプション</param>
/// <returns>true:見つかった、false:見つからなかった</returns>
/// <remarks>
/// 正規表現にエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public static bool IsMatch(string input, string pattern, string options = null)
{
using (BregonigRegex regex = new BregonigRegex(pattern, options))
{
return regex.IsMatch(input);
}
}
/// <summary>
/// 指定した入力文字列内で指定した正規表現パターンと一致した箇所を、指定した置換文字列に置換します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="replacement">置換文字列</param>
/// <param name="options">正規表現オプション:"g"(グローバルな置換)指定時は一致箇所すべて、"g"指定なしの場合は最初に一致した1箇所のみを、置換します)</param>
/// <returns>置換結果文字列。正規表現パターンと一致した箇所がなかった場合は入力文字列と同一の文字列</returns>
/// <remarks>
/// 正規表現や置換パターンにエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public static string Replace(string input, string pattern, string replacement, string options = null)
{
using (BregonigRegex regex = new BregonigRegex(pattern, options))
{
return regex.Replace(input, replacement);
}
}
#endregion
#region constructor ---------------------------------------------------
/// <summary>
/// 鬼雲正規表現クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="options">正規表現オプション(文字列で指定)</param>
/// <remarks>
/// アンマネージドリソースを使用します。使用後はDispose()してください。
/// 正規表現にエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public BregonigRegex(string pattern, string options = null)
{
if (pattern == null)
{
throw new ArgumentNullException("pattern");
}
this.pattern = pattern;
this.options = options ?? ""; // 未指定時は空文字が指定されたとみなす
this.prevoptions = this.options;
this.rxp = IntPtr.Zero;
try
{
BoMatch(pattern, options, "", 0, 0, true); // 正規表現を初期化
}
finally
{
FreeText(); // コンストラクタで例外となった場合もリソース解放は確実に実施する
}
}
/// <summary>
/// 鬼雲正規表現クラスの新しいインスタンスを初期化します。
/// </summary>
/// <param name="pattern">正規表現パターン文字列</param>
/// <param name="options">正規表現オプション(RegexOptionsで指定)</param>
/// <param name="replaceGlobal">正規表現オプションとして"g"(グローバルな置換)を指定する場合true</param>
/// <remarks>
/// アンマネージドリソースを使用します。使用後はDispose()してください。
/// 正規表現にエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// bregonig.dllの読み込みに失敗した場合は、DllNotFoundException(dllファイルがない)/TypeLoadException(BoMatchメソッドがない)/BadImageFormatException(x86/x64不一致)などの例外が投げられます。
/// </remarks>
public BregonigRegex(string pattern, RegexOptions options, bool replaceGlobal = false)
: this(pattern, ConvertOption(options, replaceGlobal)) { }
/// <summary>
/// RegexOptionsで指定された正規表現オプションを鬼雲正規表現オプション文字列へ変換します。
/// </summary>
/// <param name="options">正規表現オプション(文字列で指定)</param>
/// <param name="replaceGlobal">正規表現オプションとして"g"(グローバルな置換)を指定する場合true</param>
/// <returns>鬼雲正規表現オプション文字列</returns>
protected static string ConvertOption(RegexOptions options, bool replaceGlobal = false)
{
StringBuilder sb = new StringBuilder('u');
if (options.HasFlag(RegexOptions.IgnoreCase)) { sb.Append('i'); }
if (options.HasFlag(RegexOptions.Multiline)) { sb.Append('m'); }
if (options.HasFlag(RegexOptions.Singleline)) { sb.Append('s'); }
if (options.HasFlag(RegexOptions.IgnorePatternWhitespace)) { sb.Append('x'); }
if (replaceGlobal) { sb.Append('g'); }
return sb.ToString();
}
#endregion
#region methods -------------------------------------------------------
/// <summary>コンストラクタで指定された正規表現オプション文字列を返します。</summary>
public string Options { get { return options; } }
/// <summary>コンストラクタで指定された正規表現パターン文字列を返します。</summary>
public override string ToString()
{
return pattern;
}
/// <summary>
/// 指定した入力文字列内で、正規表現パターンと一致する箇所を検索します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="startat">検索開始文字位置(省略時は先頭から検索)</param>
/// <returns>検索によって見つかった Matched オブジェクト。見つからなかった場合はnull</returns>
public Matched Match(string input, int startat = 0)
{
return Match(input, startat, input.Length - startat);
}
/// <summary>
/// 入力文字列の指定された開始位置・文字数の範囲で、正規表現パターンと一致する箇所を検索します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="beginning">検索開始文字位置</param>
/// <param name="length">検索対象とする文字数</param>
/// <returns>検索によって見つかった Matched オブジェクト。見つからなかった場合はnull</returns>
/// <remarks>
/// 入力文字列・開始位置・文字数が妥当ではない場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// </remarks>
public Matched Match(string input, int beginning, int length)
{
if (input == null)
{
throw new ArgumentNullException("input");
}
if (beginning < 0 || beginning > input.Length)
{
throw new ArgumentOutOfRangeException("beginning", beginning, "");
}
if (length < 0 || beginning + length > input.Length)
{
throw new ArgumentOutOfRangeException("length", length, "");
}
//
return BoMatch(null, null, input, beginning, beginning + length, false);
}
/// <summary>
/// 指定した入力文字列内で、正規表現パターンと一致する箇所を検索しすべて列挙します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="startat">検索開始文字位置(省略時は先頭から検索)</param>
/// <returns>検索によって見つかった Matched オブジェクトの列挙</returns>
public IEnumerable<Matched> Matches(string input, int startat = 0)
{
Matched match = Match(input, startat);
while (match != null)
{
yield return match;
match = match.NextMatch();
}
}
/// <summary>
/// 指定した入力文字列内で、正規表現パターンと一致する箇所が見つかるかどうかを返します。
/// </summary>
/// <param name="input">検索対象とする入力文字列</param>
/// <param name="startat">検索開始文字位置(省略時は先頭から検索)</param>
/// <returns>true:見つかった、false:見つからなかった</returns>
public bool IsMatch(string input, int startat = 0)
{
return (Match(input, startat) != null);
}
/// <summary>
/// 指定した入力文字列内で正規表現パターンと一致した箇所を、指定した置換パターンで置換します。
/// </summary>
/// <param name="input">検索・置換対象とする入力文字列</param>
/// <param name="replacement">置換パターン</param>
/// <param name="startat">入力文字列中での置換対象探索開始文字位置(省略時は先頭から置換対象を探索)</param>
/// <returns>置換結果文字列。正規表現パターンと一致した箇所がなかった場合は入力文字列と同一の文字列</returns>
/// <remarks>
/// 正規表現オプション"g"(グローバルな置換)指定時は一致箇所すべて、"g"指定なしの場合は最初に一致した1箇所のみを、置換します。
/// 置換パターンにエラーがある場合は、ArgumentExceptionないしそのサブクラスの例外が投げられます。
/// </remarks>
public string Replace(string input, string replacement, int startat = 0)
{
return BoSubst(replacement, this.options, input, startat, input.Length);
}
#endregion
#region disposing -----------------------------------------------------
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (disposed) { return; }
// free managed objects
if (disposing)
{
// none
}
// free unmanaged objects
lock (pattern)
{ // dll呼び出し中に案マネージドリソースを解放してしまわないようlockで排他制御
FreeText();
if (rxp != IntPtr.Zero)
{
BRegfree(rxp);
}
rxp = IntPtr.Zero;
}
//
disposed = true;
}
~BregonigRegex()
{
Dispose(false);
}
#endregion
#region protected -----------------------------------------------------
/// <summary>コンストラクタで指定された正規表現パターン文字列</summary>
protected readonly string pattern;
/// <summary>コンストラクタで指定された正規表現オプション</summary>
protected readonly string options;
/// <summary>BREGEXP 構造体へのポインタ(要BRegFree解放)</summary>
protected IntPtr rxp;
/// <summary>直前の置換処理で指定された置換パターン</summary>
protected string prevsubst;
/// <summary>直前の置換処理で指定された正規表現オプション</summary>
protected string prevoptions;
/// <summary>入力文字列</summary>
protected string input;
/// <summary>入力文字列をアンマネージメモリに割り当てた先のポインタ(要Marshal解放)</summary>
protected IntPtr strstartp;
/// <summary>入力文字列をアンマネージメモリに割り当てます。</summary>
/// <param name="input">入力文字列</param>
protected void AllocText(string input)
{
// 検索対象の入力文字列が変わった場合のみアンマネージドメモリへ割り当て(変わっていないなら割り当てなおさない)
if (!string.ReferenceEquals(input, this.input))
{
FreeText();
this.strstartp = Marshal.StringToHGlobalUni(input);
this.input = input;
}
}
/// <summary>入力文字列をアンマネージメモリから解放します。</summary>
protected void FreeText()
{
if (this.strstartp != IntPtr.Zero)
{
Marshal.FreeHGlobal(strstartp);
this.strstartp = IntPtr.Zero;
}
}
/// <summary>
/// bregexp(bregonig)が内部で使用するコンパイルブロック構造体
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
protected struct BREGEXP
{
public IntPtr outp; // (result string start ptr)
public IntPtr outendp; // (result string end ptr)
public int splitctr; // (split result counter)
public IntPtr splitpp; // (split result pointer ptr)
public int rsv1; // (reserved for external use)
public IntPtr parap; // (parameter start ptr ie. "s/xxxxx/yy/gi")
public IntPtr paraendp; // (parameter end ptr)
public IntPtr transtblp; // (translate table ptr)
public IntPtr startpp; // マッチしたデータの先頭ポインタ(の配列。[0]=マッチ箇所全体の開始位置、[1-n]=グループ($1-$n)の開始位置)
public IntPtr endpp; // マッチしたデータの最終ポインタ+1(の配列。[0]=マッチ箇所全体の終了位置、[1-n]=グループ($1-$n)の終了位置)
public int nparens; // パターンの中の() の数。 $1,$2,...,$n を調べるときに使用
/// <summary>
/// 引数で指定された添字のGroupオブジェクトを生成します。
/// </summary>
/// <param name="i">グループ添字:[0]=マッチ箇所全体、[1]~[n]=グループ($1-$n)</param>
/// <param name="regex">正規表現オブジェクト</param>
/// <returns>Groupオブジェクト。ただし該当Groupがキャプチャされていない場合はnull</returns>
public Group CreateGroup(int i, BregonigRegex regex)
{
IntPtr startp = Marshal.ReadIntPtr(this.startpp, i * IntPtr.Size);
IntPtr endp = Marshal.ReadIntPtr(this.endpp, i * IntPtr.Size);
Group g = null;
if (startp != IntPtr.Zero)
{
g = new Group(regex.input,
(int)((startp.ToInt64() - regex.strstartp.ToInt64()) / sizeof(char)),
(int)((endp.ToInt64() - startp.ToInt64()) / sizeof(char)));
}
return g;
}
}
/// <summary>
/// BoMatch()呼び出し/結果処理簡略化
/// </summary>
/// <param name="pattern">検索パターン(NULLを指定すると、前回のコンパイル済みパターンを使用する)</param>
/// <param name="options">オプション</param>
/// <param name="input">検索対象文字列</param>
/// <param name="targetstart">検索開始文字位置</param>
/// <param name="targetend">検索終了文字位置</param>
/// <param name="one_shot">ワンショットモード:通常はfalseを指定</param>
/// <returns>検索によって見つかった Matched オブジェクト</returns>
protected Matched BoMatch(
string pattern,
string options,
string input,
int targetstart,
int targetend,
bool one_shot)
{
if (this.disposed)
{
throw new ObjectDisposedException("BregonigRegexオブジェクト");
}
lock (this.pattern)
{
// 検索位置をポインタに変換
AllocText(input);
IntPtr targetstartp = IntPtr.Add(strstartp, targetstart * sizeof(char));
IntPtr targetendp = IntPtr.Add(strstartp, targetend * sizeof(char));
StringBuilder msgSb = new StringBuilder(80); // エラーメッセージ格納用領域を確保
int ret = BoMatch(pattern, options, strstartp, targetstartp, targetendp, one_shot, ref rxp, msgSb);
if (ret < 0)
{ // エラーあり
throw new ArgumentException(msgSb.ToString());
}
if (ret > 0)
{
// マッチあり:構造体ポインタの最新の内容を取り出し
BREGEXP rx = (BREGEXP)(Marshal.PtrToStructure(rxp, typeof(BREGEXP)));
int arrayLength = rx.nparens + 1; // グループ数+[0]マッチ箇所全体 の分
Group[] groups = new Group[arrayLength];
for (int i = 0; i < arrayLength; i++)
{
groups[i] = rx.CreateGroup(i, this);
}
return new Matched(groups, this, targetstart, targetend);
}
else
{
return null;
}
}
}
/// <summary>
/// BoSubst()呼び出し/結果処理簡略化
/// </summary>
/// <param name="subst">置換パターン(NULLを指定すると、前回のコンパイル済みパターンを使用する)</param>
/// <param name="options">オプション</param>
/// <param name="input">検索対象文字列</param>
/// <param name="targetstart">検索開始文字位置</param>
/// <param name="targetend">検索終了文字位置</param>
/// <param name="match">置換対象となるMatchedオブジェクト</param>
/// <returns>置換結果文字列(matchが指定されている場合はマッチした部分の変更結果、matchがnullの場合はinput全体の置換結果)</returns>
protected string BoSubst(
string subst,
string options,
string input,
int targetstart,
int targetend,
Matched match = null)
{
if (this.disposed)
{
throw new ObjectDisposedException("BregonigRegexオブジェクト");
}
// 置換オプション/置換パターンが前回と一緒であればコンパイルを省略する
string pattern = this.pattern;
if (options == this.prevoptions)
{
pattern = null;
options = null;
if (subst == this.prevsubst)
{
subst = null;
}
else
{ // subst相違あり、今回指定されたsubstを保存
this.prevsubst = subst;
}
}
else
{ // option相違あり、今回指定されたoptionsとsubstを保存
this.prevoptions = options;
this.prevsubst = subst;
}
lock (this.pattern)
{
// 検索位置をポインタに変換
AllocText(input);
IntPtr targetstartp = IntPtr.Add(strstartp, targetstart * sizeof(char));
IntPtr targetendp = IntPtr.Add(strstartp, targetend * sizeof(char));
StringBuilder msgSb = new StringBuilder(80); // エラーメッセージ格納用領域を確保
int ret = BoSubst(pattern, subst, options, strstartp, targetstartp, targetendp, null, ref rxp, msgSb);
if (ret < 0)
{
// エラーあり
throw new ArgumentException(msgSb.ToString());
}
if (ret > 0)
{
// 置換成功:構造体ポインタの最新の内容を取り出し
BREGEXP rx = (BREGEXP)(Marshal.PtrToStructure(rxp, typeof(BREGEXP)));
if (match != null)
{
Group replacedMatch = rx.CreateGroup(0, this); // 置換された部分のマッチ情報を取得
if (match.Index != replacedMatch.Index || match.Length != replacedMatch.Length)
{
// 検索時/置換時でマッチ箇所が同一にならなかった(通常発生しない。発生したらBregonigRegexのバグ、要究明)
throw new SystemException("置換対象のマッチ箇所(Index=" + match.Index + ", Match=" + match.Length + ")"
+ "とは異なる箇所(Index=" + replacedMatch.Index + ", Match=" + replacedMatch.Length + ")が置換されました。");
}
if (rx.outp == null)
{
return string.Empty; // 置換結果が空文字となった
}
// 置換部分の開始位置=置換結果全体の開始位置+(マッチ箇所の開始位置、ただし探索開始位置考慮)
IntPtr replacedstart = IntPtr.Add(rx.outp, (match.Index - targetstart) * sizeof(char));
// 置換部分の置換後文字列長=マッチ箇所の置換前文字列長+置換結果全体の文字列長-置換前文字列全体の文字列長
int len = match.Length
+ (int)((rx.outendp.ToInt64() - rx.outp.ToInt64()) / sizeof(char))
- (targetend - targetstart);
// 置換部分の文字列内容のみをピンポイントで抜き出す
return Marshal.PtrToStringUni(replacedstart, len);
}
else
{
// 置換後文字列全体を組み立て
StringBuilder sb = new StringBuilder();
if (targetstart > 0) { sb.Append(input.Substring(0, targetstart)); }
if (rx.outp != null)
{ // 空文字列に置換されていなければ、置換結果の文字列を取り出し
int len = (int)((rx.outendp.ToInt64() - rx.outp.ToInt64()) / sizeof(char));
sb.Append(Marshal.PtrToStringUni(rx.outp, len));
}
if (targetend < input.Length) { sb.Append(input.Substring(targetend)); }
return sb.ToString();
}
}
else
{
// 置換箇所なし
if (match != null)
{
// 検索時/置換時でマッチ箇所が同一にならなかった(通常発生しない。発生したらBregonigRegexのバグ、要究明)
throw new SystemException("置換対象のマッチ箇所(Index=" + match.Index + ", Match=" + match.Length + ")が検出できませんでした。");
}
// 置換前テキスト内容のまま変更なし
return input;
}
}
}
/// <summary>
/// bregonig.dllを用いてパターンマッチ処理を実行します。
/// </summary>
/// <param name="pattern">検索パターン(NULLを指定すると、前回のコンパイル済みパターンを使用する)</param>
/// <param name="options">オプション</param>
/// <param name="strstartp">検索対象文字列</param>
/// <param name="targetstartp">検索開始位置</param>
/// <param name="targetendp">検索終了モード</param>
/// <param name="one_shot">ワンショットモード:通常はfalseを指定</param>
/// <param name="rxp">BREGEXP 構造体ポインタ (rx) へのポインタ。初回はIntPtr(0)を指定して呼び出す。</param>
/// <param name="msg">エラーメッセージの格納先。NULL不可、80文字分の領域が必要</param>
/// <returns>正:マッチ箇所あり、0:マッチ箇所なし、負:エラー</returns>
[DllImport("bregonig.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
protected static extern int BoMatch(
string pattern,
string options,
IntPtr strstartp,
IntPtr targetstartp,
IntPtr targetendp,
bool one_shot,
ref IntPtr rxp,
StringBuilder msg);
/// <summary>
/// bregonig.dllを用いて置換処理を実行します。
/// </summary>
/// <param name="pattern">検索パターン(NULLを指定すると、前回のコンパイル済みパターンを使用する)</param>
/// <param name="subst">置換パターン(NULLを指定すると、前回のコンパイル済みパターンを使用する)</param>
/// <param name="options">オプション</param>
/// <param name="strstartp">検索対象文字列</param>
/// <param name="targetstartp">検索開始位置</param>
/// <param name="targetendp">検索終了モード</param>
/// <param name="one_shot">コールバック関数へのポインタ:通常はnullを指定</param>
/// <param name="rxp">BREGEXP 構造体ポインタ (rx) へのポインタ。初回はIntPtr(0)を指定して呼び出す。</param>
/// <param name="msg">エラーメッセージの格納先。NULL不可、80文字分の領域が必要</param>
/// <returns>正:マッチ箇所あり、0:マッチ箇所なし、負:エラー</returns>
[DllImport("bregonig.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
protected static extern int BoSubst(
string pattern,
string subst,
string options,
IntPtr strstartp,
IntPtr targetstartp,
IntPtr targetendp,
[MarshalAs(UnmanagedType.FunctionPtr)] BoSubstCallbackDelegate callback,
ref IntPtr rxp,
StringBuilder msg);
/// <summary>置換コールバック関数の定義</summary>
/// <param name="kind">0 固定</param>
/// <param name="value">置換回数</param>
/// <param name="index">ptrdiff_t 置換位置</param>
/// <returns>true:処理継続、false:処理中断</returns>
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
protected delegate bool BoSubstCallbackDelegate(int kind, int value, IntPtr index);
/// <summary>
/// BREGEXPのコンパイルブロックを解放します。
/// </summary>
/// <param name="rx">BREGEXP 構造体ポインタ</param>
[DllImport("bregonig.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
protected static extern void BRegfree(IntPtr rx);
#endregion
}
}