1 #Region "Microsoft.VisualBasic::5bb2ac444add8e90fd59a98852a81e2e, Microsoft.VisualBasic.Core\Extensions\StringHelpers\StringHelpers.vb"
2
3 ' Author:
4
5 '       asuka (amethyst.asuka@gcmodeller.org)
6 '       xie (genetics@smrucc.org)
7 '       xieguigang (xie.guigang@live.com)
8
9 ' Copyright (c) 2018 GPL3 Licensed
10
11
12 ' GNU GENERAL PUBLIC LICENSE (GPL3)
13
14
15 ' This program is free software: you can redistribute it and/or modify
16 ' it under the terms of the GNU General Public License as published by
17 ' the Free Software Foundation, either version 3 of the License, or
18 ' (at your option) any later version.
19
20 ' This program is distributed in the hope that it will be useful,
21 ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23 ' GNU General Public License for more details.
24
25 ' You should have received a copy of the GNU General Public License
26 ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30 ' /********************************************************************************/
31
32 ' Summaries:
33
34 Module StringHelpers
35
36 '     Properties: NonStrictCompares, StrictCompares
37
38 '     Function: __json, AllEquals, CharAtOrDefault, CharString, (+3 Overloads) Count
39 '               CreateBuilder, DistinctIgnoreCase, EqualsAny, First, FormatString
40 '               FormatZero, GetBetween, GetEMails, GetStackValue, GetString
41 '               (+2 OverloadsGetTagValue, GetURLs, IgnoreCase, InStrAny, (+2 OverloadsIntersection
42 '               IsEmptyStringVector, IsNullOrEmpty, JoinBy, LineTokens, Located
43 '               Lookup, (+2 Overloads) Match, Matches, MatchPattern, (+2 Overloads) MaxLengthString
44 '               Parts, RepeatString, ReplaceChars, (+2 Overloads) Reverse, RNull
45 '               SaveTo, (+2 Overloads) Split, SplitBy, StringEmpty, StringHashCode
46 '               StringReplace, StringSplit, StripBlank, Strips, TextEquals
47 '               TextLast, TokenCount, TokenCountIgnoreCase, ToTruncateInt32, ToTruncateInt64
48 '               TrimA, TrimNewLine, WildcardsLocated
49
50 '     Sub: Parts, RemoveLast
51
52 ' /********************************************************************************/
53
54 #End Region
55
56 Imports System.Drawing
57 Imports System.Numerics
58 Imports System.Runtime.CompilerServices
59 Imports System.Text
60 Imports System.Text.RegularExpressions
61 Imports Microsoft.VisualBasic.CommandLine.Reflection
62 Imports Microsoft.VisualBasic.ComponentModel
63 Imports Microsoft.VisualBasic.ComponentModel.DataSourceModel
64 Imports Microsoft.VisualBasic.Language
65 Imports Microsoft.VisualBasic.Language.Default
66 Imports Microsoft.VisualBasic.Linq
67 Imports Microsoft.VisualBasic.Linq.Extensions
68 Imports Microsoft.VisualBasic.Math.Information
69 Imports Microsoft.VisualBasic.Scripting.MetaData
70 Imports Microsoft.VisualBasic.Serialization.JSON
71 Imports Microsoft.VisualBasic.Text
72 Imports Microsoft.VisualBasic.Text.Patterns
73 Imports r = System.Text.RegularExpressions.Regex
74
75 ''' <summary>
76 ''' The extensions module for facilities the string operations.
77 ''' </summary>
78 <Package("StringHelpers", Publisher:="amethyst.asuka@gcmodeller.org", Url:="http://gcmodeller.org")>
79 Public Module StringHelpers
80
81     <Extension>
82     Public Function CharAtOrDefault(s$, index%, Optional [default] As Char = ASCII.NUL) As Char
83         If s.Length <= index Then
84             Return [default]
85         Else
86             Return s(index)
87         End If
88     End Function
89
90     ''' <summary>
91     ''' Get the first char of the target <see cref="StringBuilder"/> 
92     ''' </summary>
93     ''' <param name="sb"></param>
94     ''' <returns></returns>
95     <MethodImpl(MethodImplOptions.AggressiveInlining)>
96     <Extension>
97     Public Function First(sb As StringBuilder) As Char
98         Return sb.Chars(Scan0)
99     End Function
100
101     <MethodImpl(MethodImplOptions.AggressiveInlining)>
102     <Extension>
103     Public Function CreateBuilder(s As StringAs StringBuilder
104         Return New StringBuilder(s)
105     End Function
106
107     <MethodImpl(MethodImplOptions.AggressiveInlining)>
108     Public Function IgnoreCase(flag As BooleanAs CompareMethod
109         Return If(flag, CompareMethod.Text, CompareMethod.Binary)
110     End Function
111
112     ''' <summary>
113     ''' Using <see cref="[String].Empty"/> as default value
114     ''' </summary>
115     Public ReadOnly EmptyString As DefaultValue(Of String) = String.Empty
116
117     ''' <summary>
118     ''' Replace the <see cref="vbCrLf"/> with the specific string.
119     ''' </summary>
120     ''' <param name="src"></param>
121     ''' <param name="VbCRLF_Replace"></param>
122     ''' <returns></returns>
123 #If FRAMEWORD_CORE Then
124     <ExportAPI("Trim")>
125     <Extension> Public Function TrimNewLine(src$, <Parameter("vbCrLf.Replaced")> Optional VbCRLF_Replace$ = " "As String
126 #Else
127     <Extension> Public Function TrimA(strText As StringOptional VbCRLF_Replace As String = " "As String
128 #End If
129         If src Is Nothing Then
130             Return ""
131         End If
132
133         src = src.Replace(vbCrLf, VbCRLF_Replace) _
134                  .Replace(vbCr, VbCRLF_Replace) _
135                  .Replace(vbLf, VbCRLF_Replace) _
136                  .Replace("  "" ")
137
138         Return Strings.Trim(src)
139     End Function
140
141     <Extension>
142     Public Function ReplaceChars(src$, chars As IEnumerable(Of Char), replaceAs As CharAs String
143         Dim s As New StringBuilder(src)
144
145         For Each c As Char In chars
146             Call s.Replace(c, replaceAs)
147         Next
148
149         Return s.ToString
150     End Function
151
152     ''' <summary>
153     ''' 判断这个字符串数组之中的所有的元素都是空字符串?
154     ''' </summary>
155     ''' <param name="s$">字符串数组</param>
156     ''' <returns></returns>
157     <Extension> Public Function IsEmptyStringVector(s$(), Optional RNull As Boolean = FalseAs Boolean
158         If RNull Then
159             Return s _
160                 .Where(AddressOf StringHelpers.RNull) _
161                 .Count = s.Length
162         Else
163             Return s.Where(Function(c) Not c.StringEmpty).Count = 0
164         End If
165     End Function
166
167     ''' <summary>
168     ''' Is text equals to the R nothing?
169     ''' </summary>
170     ''' <param name="c$"></param>
171     ''' <returns></returns>
172     Private Function RNull(c$) As Boolean
173         Return c.StringEmpty OrElse
174                c.TextEquals("NULL"OrElse
175                c.TextEquals("NA")
176     End Function
177
178     <Extension>
179     Public Function AllEquals(s As IEnumerable(Of String), str$) As Boolean
180         Return s.All(Function(x) x = str)
181     End Function
182
183     ''' <summary>
184     ''' https://github.com/darkskyapp/string-hash
185     ''' </summary>
186     ''' <param name="s$"></param>
187     ''' <returns></returns>
188     <Extension> Public Function StringHashCode(s As StringAs Long
189         Dim hash& = 5381
190         Dim chars%() = s.Select(AddressOf Convert.ToInt32).ToArray
191
192         For i As Integer = s.Length - 1 To 0 Step -1
193             hash = (New BigInteger(hash) * 33 Xor chars(i)).ToTruncateInt64
194         Next
195
196         hash = hash >> 0
197
198         Return hash
199     End Function
200
201     ''' <summary>
202     ''' 常用于gdi+绘图操作,和<see cref="Graphics.MeasureString"/>共同使用
203     ''' </summary>
204     ''' <param name="source"></param>
205     ''' <returns></returns>
206     <Extension>
207     Public Function MaxLengthString(source As IEnumerable(Of String)) As String
208         Return source.OrderByDescending(Function(s) Len(s)).First
209     End Function
210
211     ''' <summary>
212     ''' 获取最大长度的字符串
213     ''' </summary>
214     ''' <typeparam name="T"></typeparam>
215     ''' <param name="source"></param>
216     ''' <param name="getString"></param>
217     ''' <returns></returns>
218     <Extension>
219     Public Function MaxLengthString(Of T)(source As IEnumerable(Of T), getString As Func(Of T, String)) As String
220         Return source.Select(getString).MaxLengthString
221     End Function
222
223     ''' <summary>
224     ''' 将<paramref name="replaces"/>列表之中的字符串都替换为空字符串
225     ''' </summary>
226     ''' <param name="s$"></param>
227     ''' <param name="replaces"></param>
228     ''' <returns></returns>
229     <Extension>
230     Public Function Strips(s$, replaces As IEnumerable(Of String)) As String
231         Dim sb As New StringBuilder(s)
232
233         For Each r As String In replaces
234             Call sb.Replace(r, "")
235         Next
236
237         Return sb.ToString
238     End Function
239
240     ''' <summary>
241     ''' 将一个任意的目标字符集合转换为字符串对象
242     ''' </summary>
243     ''' <param name="chs"></param>
244     ''' <returns></returns>
245     <Extension>
246     Public Function CharString(chs As IEnumerable(Of Char)) As String
247         Return New String(chs.ToArray)
248     End Function
249
250     '
251     ' Summary:
252     '     Replaces the format item in a specified string with the string representation
253     '     of a corresponding object in a specified array.
254     '
255     ' Parameters:
256     '   format:
257     '     A composite format string.
258     '
259     '   args:
260     '     An object array that contains zero or more objects to format.
261     '
262     Returns:
263     '     A copy of format in which the format items have been replaced by the string representation
264     '     of the corresponding objects in args.
265     '
266     ' Exceptions:
267     '   T:System.ArgumentNullException:
268     '     format or args is null.
269     '
270     '   T:System.FormatException:
271     '     format is invalid.-or- The index of a format item is less than zero, or greater
272     '     than or equal to the length of the args array.
273
274     ''' <summary>
275     ''' Replaces the format item in a specified string with the string representation
276     ''' of a corresponding object in a specified array.
277     ''' </summary>
278     ''' <param name="s">A composite format string.</param>
279     ''' <param name="args">An object array that contains zero or more objects to format.</param>
280     ''' <returns>A copy of format in which the format items have been replaced by the string representation
281     ''' of the corresponding objects in args.</returns>
282     <Extension>
283     Public Function FormatString(s$, ParamArray args As Object()) As String
284         Return String.Format(s, args)
285     End Function
286
287     ''' <summary>
288     ''' this is to emulate what's evailable in PHP
289     ''' </summary>
290     <Extension>
291     Public Function RepeatString(text$, count%) As String
292         Dim sb = New StringBuilder(text.Length * count)
293         For i As Integer = 0 To count - 1
294             Call sb.Append(text)
295         Next
296         Return sb.ToString()
297     End Function
298
299     ''' <summary>
300     ''' Join and contact the text tokens with a specific <paramref name="delimiter"/> string.
301     ''' </summary>
302     ''' <typeparam name="T"></typeparam>
303     ''' <param name="data"></param>
304     ''' <param name="delimiter$"></param>
305     ''' <returns></returns>
306     ''' 
307     <MethodImpl(MethodImplOptions.AggressiveInlining)>
308     <Extension>
309     Public Function JoinBy(Of T)(data As IEnumerable(Of T), delimiter$) As String
310         Return String.Join(delimiter, data.SafeQuery.Select(AddressOf Scripting.ToString).ToArray)
311     End Function
312
313     ''' <summary>
314     ''' Text parser for the format: ``tagName{<paramref name="delimiter"/>}value``
315     ''' </summary>
316     ''' <param name="s"></param>
317     ''' <param name="delimiter"></param>
318     ''' <param name="trim">Needs Removes all leading and trailing white-space characters from 
319     ''' the current <see cref="System.String"/> object.</param>
320     ''' <returns></returns>
321     ''' 
322     <MethodImpl(MethodImplOptions.AggressiveInlining)>
323     <Extension>
324     Public Function GetTagValue(s$, Optional delimiter$ = " "Optional trim As Boolean = FalseOptional failureNoName As Boolean = TrueAs NamedValue(Of String)
325         Return s.GetTagValue(delimiter, trim:=If(trim, " "Nothing), failureNoName:=failureNoName)
326     End Function
327
328     ''' <summary>
329     ''' Text parser for the format: ``tagName{<paramref name="delimiter"/>}value``
330     ''' </summary>
331     ''' <param name="s$"></param>
332     ''' <param name="delimiter$"></param>
333     ''' <param name="trim$">Chars collection for <see cref="String.Trim"/> function</param>
334     ''' <param name="failureNoName"></param>
335     ''' <returns></returns>
336     <Extension>
337     Public Function GetTagValue(s$, delimiter$, trim$, Optional failureNoName As Boolean = TrueAs NamedValue(Of String)
338         If s.StringEmpty Then
339             Return Nothing
340         End If
341
342         Dim p% = InStr(s, delimiter, CompareMethod.Text)
343
344         If p = 0 Then
345             If failureNoName Then
346                 Return New NamedValue(Of String)("", s, s)
347             Else
348                 Return New NamedValue(Of String)(s, "", s)
349             End If
350         Else
351             Dim key$ = Mid(s, 1, p - 1)
352             Dim value$ = Mid(s, p + delimiter.Length)
353
354             If Not trim.StringEmpty(whitespaceAsEmpty:=False) Then
355                 With trim.ToArray
356                     value = value.Trim(.ByRef)
357                     key = key.Trim(.ByRef)
358                 End With
359             End If
360
361             Return New NamedValue(Of String)(key, value, s)
362         End If
363     End Function
364
365     <Extension>
366     Public Function StripBlank(s$, Optional includeNewline As Boolean = TrueAs String
367         If includeNewline Then
368             Return s.Trim(" "c, ASCII.TAB, ASCII.LF, ASCII.CR)
369         Else
370             Return s.Trim(" "c, ASCII.TAB)
371         End If
372     End Function
373
374     ''' <summary>
375     ''' Shortcuts for method <see cref="System.String.Equals"/>(s1, s2, <see cref="StringComparison.OrdinalIgnoreCase"/>)
376     ''' </summary>
377     ''' <param name="s1"></param>
378     ''' <param name="s2"></param>
379     ''' <returns></returns>
380     ''' 
381     <MethodImpl(MethodImplOptions.AggressiveInlining)>
382     <Extension>
383     Public Function TextEquals(s1$, s2$) As Boolean
384         'If {s1, s2}.All(Function(s) s Is NothingThen
385         '    Return True ' null = null ??
386         'End If
387         Return String.Equals(s1, s2, StringComparison.OrdinalIgnoreCase)
388     End Function
389
390     ''' <summary>
391     ''' <see cref="RegexOptions.IgnoreCase"/> + <see cref="RegexOptions.Singleline"/> 
392     ''' </summary>
393     Public Const RegexICSng As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.Singleline
394
395     ''' <summary>
396     ''' <see cref="RegexOptions.IgnoreCase"/> + <see cref="RegexOptions.Multiline"/> 
397     ''' </summary>
398     Public Const RegexICMul As RegexOptions = RegexOptions.IgnoreCase Or RegexOptions.Multiline
399
400     ''' <summary>
401     ''' <paramref name="s"/> Is Nothing, <see cref="System.String.IsNullOrEmpty"/>, <see cref="System.String.IsNullOrWhiteSpace"/>
402     ''' </summary>
403     ''' <param name="s">The input test string</param>
404     ''' <returns></returns>
405     <Extension> Public Function StringEmpty(s$, Optional whitespaceAsEmpty As Boolean = TrueAs Boolean
406         If s Is Nothing OrElse String.IsNullOrEmpty(s) Then
407             Return True
408         Else
409             If String.IsNullOrWhiteSpace(s) Then
410                 Return whitespaceAsEmpty
411             Else
412                 Return False
413             End If
414         End If
415     End Function
416
417     ''' <summary>
418     ''' Not <see cref="StringEmpty(String, Boolean)"/>
419     ''' </summary>
420     ''' <param name="s$"></param>
421     ''' <param name="whitespaceAsEmpty"></param>
422     ''' <returns></returns>
423     <MethodImpl(MethodImplOptions.AggressiveInlining)>
424     <Extension>
425     Public Function NotEmpty(s$, Optional whitespaceAsEmpty As Boolean = TrueAs Boolean
426         Return Not s.StringEmpty(whitespaceAsEmpty)
427     End Function
428
429     ''' <summary>
430     ''' Call <see cref="StringBuilder.Remove"/>(<see cref="StringBuilder.Length"/> - 1, 1) for removes the last character in the string sequence.
431     ''' </summary>
432     ''' <param name="s"></param>
433     ''' 
434     <MethodImpl(MethodImplOptions.AggressiveInlining)>
435     <Extension> Public Sub RemoveLast(s As StringBuilder)
436         Call s.Remove(s.Length - 1, 1)
437     End Sub
438
439     ''' <summary>
440     ''' Returns a reversed version of String s.
441     ''' </summary>
442     ''' <param name="sb"></param>
443     ''' <returns></returns>
444     <Extension> Public Function Reverse(ByRef sb As StringBuilder) As StringBuilder
445         Dim s As String = New String(sb.ToString.Reverse.ToArray)
446         sb = New StringBuilder(s)
447         Return sb
448     End Function
449
450     ''' <summary>
451     ''' Returns a reversed version of String s.
452     ''' </summary>
453     ''' <param name="s"></param>
454     ''' <returns></returns>
455     ''' 
456     <MethodImpl(MethodImplOptions.AggressiveInlining)>
457     Public Function Reverse(s As StringAs String
458         Return New String(s.Reverse.ToArray)
459     End Function
460
461     Public ReadOnly Property StrictCompares As StringComparison = StringComparison.Ordinal
462     ''' <summary>
463     ''' String compares with ignored chars' case.(忽略大小写为非严格的比较)
464     ''' </summary>
465     ''' <returns></returns>
466     Public ReadOnly Property NonStrictCompares As StringComparison = StringComparison.OrdinalIgnoreCase
467
468     ''' <summary>
469     ''' Split long text data into seperate lines by the specific <paramref name="len"/> value.
470     ''' </summary>
471     ''' <param name="s"></param>
472     ''' <param name="len"></param>
473     ''' <returns></returns>
474     ''' <remarks>Using for the Fasta sequence writer.</remarks>
475     <ExportAPI("s.Parts")>
476     <Extension> Public Function Parts(s$, len%) As String
477         Dim sbr As New StringBuilder
478         Call Parts(s, len, sbr)
479         Return sbr.ToString
480     End Function
481
482     Public Sub Parts(s As String, len As StringByRef sbr As StringBuilder)
483         Do While Not String.IsNullOrEmpty(s)
484             Call sbr.Append(Mid(s, 1, len))
485             s = Mid(s, len + 1)
486             If String.IsNullOrEmpty(s) Then
487                 Return
488             End If
489             Dim fs As Integer = InStr(s, " ")
490
491             If fs = 0 Then
492                 Call sbr.AppendLine(s)
493                 Return
494             End If
495
496             Dim Firts As String = Mid(s, 1, fs - 1)
497             s = Mid(s, fs + 1)
498             Call sbr.AppendLine(Firts)
499         Loop
500     End Sub
501
502     ''' <summary>
503     ''' Regex expression for parsing E-Mail URL
504     ''' </summary>
505     Const REGEX_EMAIL As String = "[a-z0-9\._-]+@[a-z0-9\._-]+"
506     ''' <summary>
507     ''' Regex exprression for parsing the http/ftp URL
508     ''' </summary>
509     Const REGEX_URL As String = "(ftp|http(s)?)[:]//[a-z0-9\.-_]+\.[a-z]+/*[^""]*"
510
511     <ExportAPI("Parsing.E-Mails")>
512     Public Function GetEMails(s As StringAs String()
513         Dim values$() = Regex _
514             .Matches(s, REGEX_EMAIL, RegexICSng) _
515             .ToArray
516         Return values
517     End Function
518
519     <ExportAPI("Parsing.URLs")>
520     Public Function GetURLs(s As StringAs String()
521         Dim values$() = Regex _
522             .Matches(s, REGEX_URL, RegexICSng) _
523             .ToArray
524         Return values
525     End Function
526
527     ''' <summary>
528     ''' Counts the specific char that appeared in the input string.
529     ''' (计数在字符串之中所出现的指定的字符的出现的次数)
530     ''' </summary>
531     ''' <param name="str"></param>
532     ''' <param name="ch"></param>
533     ''' <returns></returns>
534     '''
535     <ExportAPI("Count"Info:="Counting the specific char in the input string value.")>
536     <Extension> Public Function Count(str$, ch As CharAs Integer
537         If String.IsNullOrEmpty(str) Then
538             Return 0
539         Else
540             Dim n As Integer = str _
541                 .Where(Function(c) c = ch) _
542                 .Count
543             Return n%
544         End If
545     End Function
546
547     ''' <summary>
548     ''' 计算目标字符串在序列之中出现的次数
549     ''' </summary>
550     ''' <param name="source"></param>
551     ''' <param name="target$"></param>
552     ''' <param name="method"></param>
553     ''' <returns></returns>
554     <MethodImpl(MethodImplOptions.AggressiveInlining)>
555     <Extension>
556     Public Function Count(source As IEnumerable(Of String), target$, Optional method As StringComparison = StringComparison.Ordinal) As Integer
557         Return source _
558             .Where(Function(s) String.Equals(s, target, method)) _
559             .Count
560     End Function
561
562     ''' <summary>
563     ''' Count the phrase in <paramref name="text"/>
564     ''' </summary>
565     ''' <param name="text$"></param>
566     ''' <param name="phrase$"></param>
567     ''' <param name="method"></param>
568     ''' <returns></returns>
569     <Extension>
570     Public Function Count(text$, phrase$, Optional method As CompareMethod = CompareMethod.Binary) As Integer
571         Dim n%
572         Dim pos% = InStr(text, phrase, method)
573
574         Do While pos > 0
575             n += 1
576             pos = InStr(pos + 1, text, phrase, method)
577         Loop
578
579         Return n
580     End Function
581
582     ''' <summary>
583     ''' 获取""或者其他字符所包围的字符串的值,请注意,假若只有一个<paramref name="wrapper"/>的话,字符串将不会进行任何处理
584     ''' </summary>
585     ''' <param name="s"></param>
586     ''' <param name="wrapper"></param>
587     ''' <returns></returns>
588     ''' <remarks></remarks>
589     '''
590     <ExportAPI("Wrapper.Trim")>
591     <Extension> Public Function GetString(s$, Optional wrapper As Char = ASCII.Quot) As String
592         If String.IsNullOrEmpty(s) OrElse Len(s) = 1 Then
593             Return s
594         End If
595         If s.First = wrapper AndAlso s.Last = wrapper Then
596             Return Mid(s, 2, Len(s) - 2)
597         Else
598             Return s
599         End If
600     End Function
601
602     ''' <summary>
603     ''' Get sub string value from the region that between the <paramref name="left"/> and <paramref name="right"/>.
604     ''' (这个函数是直接分别查找左右两边的定位字符串来进行切割的) 
605     ''' </summary>
606     ''' <param name="str$"></param>
607     ''' <param name="left$"></param>
608     ''' <param name="right$"></param>
609     ''' <returns></returns>
610     <ExportAPI("Get.Stackvalue")>
611     <Extension>
612     Public Function GetStackValue(str$, left$, right$) As String
613         If Len(str) < 2 Then
614             Return ""
615         End If
616
617         Dim p As Integer = InStr(str, left) + left.Length
618         Dim q As Integer = InStrRev(str, right)
619
620         If p = 0 Or q = 0 Then
621             Return str
622         ElseIf p >= q Then
623             Return ""
624         Else
625             str = Mid(str, p, q - p)
626             Return str
627         End If
628     End Function
629
630     ''' <summary>
631     ''' 和<see cref="GetStackValue(String, StringString)"/>相似,这个函数也是查找起始和终止字符串之间的字符串,
632     ''' 但是这个函数是查找相邻的两个标记,而非像<see cref="GetStackValue(String, StringString)"/>函数一样
633     ''' 直接查找字符串的两端的定位结果
634     ''' </summary>
635     ''' <param name="str$"></param>
636     ''' <param name="strStart$"></param>
637     ''' <param name="strEnd$"></param>
638     ''' <returns></returns>
639     ''' 
640     <Extension>
641     Public Function GetBetween(str$, strStart$, strEnd$) As String
642         Dim start%, end%
643
644         If str.StringEmpty Then
645             Return Nothing
646         End If
647
648         If str.Contains(strStart) AndAlso str.Contains(strEnd) Then
649             start = str.IndexOf(strStart, 0) + strStart.Length
650             [end] = str.IndexOf(strEnd, start)
651
652             Return str.Substring(start, [end] - start)
653         Else
654             Return Nothing
655         End If
656     End Function
657
658     ''' <summary>
659     ''' 在字符串前面填充指定长度的00序列,假若输入的字符串长度大于fill的长度,则不再进行填充
660     ''' </summary>
661     ''' <typeparam name="T">限定类型为字符串或者数值基础类型</typeparam>
662     ''' <param name="n"></param>
663     ''' <param name="fill"></param>
664     ''' <returns></returns>
665     <ExportAPI("FormatZero")>
666     <Extension> Public Function FormatZero(Of T As {IComparable(Of T)})(n As T, Optional fill$ = "00"As String
667         Dim s As String = n.ToString
668         Dim d As Integer = Len(fill) - Len(s)
669
670         If d < 0 Then
671             Return s
672         Else
673             Return Mid(fill, 1, d) & s
674         End If
675     End Function
676
677     ''' <summary>
678     ''' 求交集
679     ''' </summary>
680     ''' <param name="source"></param>
681     ''' <returns></returns>
682     ''' <remarks></remarks>
683     '''
684     <ExportAPI("Intersection")>
685     <Extension> Public Function Intersection(source As IEnumerable(Of IEnumerable(Of String))) As String()
686         Dim union As New List(Of String)
687
688         source = (From line As IEnumerable(Of String)
689                   In source
690                   Select (From s As String
691                           In line
692                           Select s
693                           Distinct
694                           Order By s Ascending).ToArray).ToArray
695
696         For Each line As String() In source
697             union += line
698         Next
699
700         union = (From s As String
701                  In union
702                  Select s
703                  Distinct
704                  Order By s Ascending).AsList  '获取并集,接下来需要从并集之中去除在两个集合之中都不存在的
705
706         For Each Line As IEnumerable(Of StringIn source
707             For Each row In source       '遍历每一个集合
708                 Dim LQuery As IEnumerable(Of String) = From s As String
709                                                        In row
710                                                        Where Array.IndexOf(Line, s) = -1
711                                                        Select s
712                 For Each s As String In LQuery
713                     Call union.Remove(s) '假若line之中存在不存在的元素,则从并集之中移除
714                 Next
715             Next
716         Next
717
718         Return union.ToArray  ' 剩下的元素都是在所有的序列之中都存在的,既交集元素
719     End Function
720
721     ''' <summary>
722     ''' 求交集
723     ''' </summary>
724     ''' <returns></returns>
725     ''' <remarks></remarks>
726     '''
727     <ExportAPI("Intersection")>
728     Public Function Intersection(ParamArray values$()()) As String()
729         Return values.Intersection
730     End Function
731
732     ''' <summary>
733     ''' Does this input string is matched by the specific regex expression?
734     ''' (判断所输入的整个字符串是否为进行判断的<paramref name="regex"/>模式,
735     ''' 即使用正则表达式所匹配的结果字符串和所输入的字符串一致)
736     ''' </summary>
737     ''' <param name="str"></param>
738     ''' <param name="regex"></param>
739     ''' <returns></returns>
740     ''' 
741     <MethodImpl(MethodImplOptions.AggressiveInlining)>
742     <ExportAPI("Matched?")>
743     <Extension> Public Function MatchPattern(str$, regex$, Optional opt As RegexOptions = RegexICSng) As Boolean
744         If str.StringEmpty Then
745             Return False
746         Else
747             Return r.Match(str, regex, opt).Success
748         End If
749     End Function
750
751     ''' <summary>
752     ''' Searches the specified input string for the first occurrence of the specified regular expression.
753     ''' </summary>
754     ''' <param name="input">The string to search for a match.</param>
755     ''' <param name="pattern">The regular expression pattern to match.</param>
756     ''' <param name="options"></param>
757     ''' <returns></returns>
758     <ExportAPI("Regex"Info:="Searches the specified input string for the first occurrence of the specified regular expression.")>
759     <Extension> Public Function Match(<Parameter("input""The string to search for a match.")> input$,
760                                       <Parameter("Pattern""The regular expression pattern to match.")> pattern$,
761                                       Optional options As RegexOptions = RegexOptions.Multiline) As String
762         If input.StringEmpty Then
763             Return ""
764         Else
765             Return r.Match(input, pattern, options).Value
766         End If
767     End Function
768
769     ''' <summary>
770     ''' Get regex match value from the target input string.
771     ''' </summary>
772     ''' <param name="input"></param>
773     ''' <param name="pattern"></param>
774     ''' <param name="options"></param>
775     ''' <returns></returns>
776     <ExportAPI("Match")>
777     <Extension> Public Function Match(input As Match, pattern$, Optional options As RegexOptions = RegexOptions.Multiline) As String
778         Return r.Match(input.Value, pattern, options).Value
779     End Function
780
781     <MethodImpl(MethodImplOptions.AggressiveInlining)>
782     <Extension>
783     Public Function Matches(input As String, pattern$, Optional options As RegexOptions = RegexICSng) As IEnumerable(Of String)
784         If input Is Nothing OrElse input.Length = 0 Then
785             Return {}
786         Else
787             Return r.Matches(input, pattern, options).EachValue
788         End If
789     End Function
790
791     ''' <summary>
792     ''' Save this string dictionary object as json file.
793     ''' </summary>
794     ''' <param name="dict"></param>
795     ''' <param name="path"></param>
796     ''' <returns></returns>
797     ''' <remarks>
798     ''' 其实,对于字典类型是可以直接使用JSON序列化得到json字符串的,但是在这里是需要
799     ''' 保存接口类型的对象,但是在这里不能够将接口类型进行json序列化,所以进行字符串
800     ''' 的序列化然后拼接出json数据
801     ''' </remarks>
802     <Extension>
803     <ExportAPI("Write.Dictionary")>
804     Public Function SaveTo(dict As IDictionary(Of StringString), path$) As Boolean
805         Dim lines$() = dict.Select(AddressOf __json).ToArray
806         Dim json$ = "{" &
807             vbTab & String.Join("," & vbCrLf & vbTab, lines) &
808         "}"
809
810         Return json.SaveTo(path, TextEncodings.UTF8WithoutBOM)
811     End Function
812
813     Private Function __json(x As KeyValuePair(Of StringString)) As String
814         Return x.Key.GetJson & ": " & x.Value.GetJson
815     End Function
816
817     ''' <summary>
818     ''' Count the string value numbers.(请注意,这个函数是倒序排序的)
819     ''' </summary>
820     ''' <param name="tokens"></param>
821     ''' <returns></returns>
822     ''' <remarks></remarks>
823     <ExportAPI("Tokens.Count"Info:="Count the string value numbers.")>
824     <Extension> Public Function TokenCount(tokens As IEnumerable(Of String), Optional ignoreCase As Boolean = FalseAs Dictionary(Of StringInteger)
825         If Not ignoreCase Then ' 大小写敏感
826             With From s As String
827                  In tokens
828                  Select s
829                  Group s By s Into Count
830
831                 Return .ToDictionary(Function(x) x.s,
832                                      Function(x) x.Count)
833             End With
834         Else
835             Return tokens.TokenCountIgnoreCase
836         End If
837     End Function
838
839     <Extension>
840     Public Function TokenCountIgnoreCase(tokens As IEnumerable(Of String)) As Dictionary(Of StringInteger)
841         With tokens.ToArray
842             Dim uniques = From s As String
843                           In .Distinct
844                           Let data As String = s
845                           Select UNIQUE_KEY = s.ToLower, data
846                           Group By UNIQUE_KEY Into Group
847
848             Dim LQuery = From ustr
849                          In uniques
850                          Let s As String = ustr.UNIQUE_KEY
851                          Let count As Integer = .Count(s, StringComparison.OrdinalIgnoreCase)
852                          Select key = ustr.Group.First.data, count
853                          Order By count Descending
854
855             Dim result = LQuery.ToDictionary(
856                 Function(x) x.key,
857                 Function(x) x.count)
858
859             Return result
860         End With
861     End Function
862
863     ''' <summary>
864     ''' This method is used to replace most calls to the Java <see cref="[String].Split"/> method.
865     ''' </summary>
866     ''' <param name="source"></param>
867     ''' <param name="pattern"><see cref="Regex"/> patterns</param>
868     ''' <param name="trimTrailingEmptyStrings"></param>
869     ''' <returns></returns>
870     ''' <remarks></remarks>
871     <ExportAPI("StringsSplit"Info:="This method is used to replace most calls to the Java String.split method.")>
872     <Extension> Public Function StringSplit(source$, pattern$,
873                                             Optional TrimTrailingEmptyStrings As Boolean = False,
874                                             Optional opt As RegexOptions = RegexICSng) As String()
875         If source.StringEmpty Then
876             Return {}
877         End If
878
879         Dim splitArray$() = Regex.Split(source, pattern, options:=opt)
880
881         If Not TrimTrailingEmptyStrings OrElse splitArray.Length <= 1 Then
882             Return splitArray
883         Else
884             Return splitArray _
885                 .Where(Function(s)
886                            Return Not String.IsNullOrEmpty(s)
887                        End Function) _
888                 .ToArray
889         End If
890     End Function
891
892     ''' <summary>
893     ''' Alias for <see cref="Strings.Split(String, StringInteger, CompareMethod)"/>
894     ''' </summary>
895     ''' <param name="str$"></param>
896     ''' <param name="deli$"></param>
897     ''' <returns></returns>
898     <MethodImpl(MethodImplOptions.AggressiveInlining)>
899     <Extension>
900     Public Function SplitBy(str$, deli$, Optional compares As CompareMethod = CompareMethod.Text) As String()
901         Return Strings.Split(str, deli, Compare:=compares)
902     End Function
903
904     ''' <summary>
905     ''' 将正则匹配成功的字符串替换为指定的目标字符串:<paramref name="replaceAs"/>
906     ''' </summary>
907     ''' <param name="s$"></param>
908     ''' <param name="pattern$"></param>
909     ''' <param name="replaceAs$"></param>
910     ''' <param name="opt"></param>
911     ''' <returns></returns>
912     <Extension>
913     Public Function StringReplace(s$, pattern$, replaceAs$, Optional opt As RegexOptions = RegexICSng) As String
914         Dim targets$() = r.Matches(s, pattern, opt).ToArray
915         Dim sb As New StringBuilder(s)
916
917         For Each t As String In targets
918             Call sb.Replace(t, replaceAs)
919         Next
920
921         Return sb.ToString
922     End Function
923
924     ''' <summary>
925     ''' String collection tokenized by a certain delimiter string element.
926     ''' </summary>
927     ''' <param name="source"></param>
928     ''' <param name="delimiter">
929     ''' Using ``String.Equals`` or Regular expression function to determined this delimiter 
930     ''' </param>
931     ''' <returns></returns>
932     <Extension> Public Function Split(source As IEnumerable(Of String),
933                                       delimiter$,
934                                       Optional regex As Boolean = False,
935                                       Optional opt As RegexOptions = RegexOptions.Singleline) As IEnumerable(Of String())
936
937         Dim delimiterTest As Assert(Of String)
938
939         If regex Then
940             With New Regex(delimiter, opt)
941                 delimiterTest = Function(line)
942                                     Return .Match(line).Value = line
943                                 End Function
944             End With
945         Else
946             delimiterTest = Function(line)
947                                 Return String.Equals(delimiter, line, StringComparison.Ordinal)
948                             End Function
949         End If
950
951         Return source.Split(delimiterTest, includes:=False)
952     End Function
953
954     ''' <summary>
955     ''' 这个函数适合将一个很大的数组进行分割
956     ''' </summary>
957     ''' <param name="source"></param>
958     ''' <param name="assertionDelimiter">分隔符断言,判断当前的对象是不是分隔符</param>
959     ''' <param name="includes"></param>
960     ''' <returns></returns>
961     <Extension>
962     Public Iterator Function Split(source As IEnumerable(Of String),
963                                    assertionDelimiter As Assert(Of String),
964                                    Optional includes As Boolean = TrueAs IEnumerable(Of String())
965
966         Dim list As New List(Of String)
967         Dim first As Boolean = True  ' first line
968
969         For Each line As String In source
970             If True = assertionDelimiter(line) Then
971                 If first Then
972                     first = False
973                 Else
974                     Yield list.ToArray
975
976                     list.Clear()
977                 End If
978
979                 If includes Then
980                     list.Add(line)
981                 End If
982             Else
983                 Call list.Add(line)
984             End If
985
986             first = False
987         Next
988
989         If list.Count > 0 Then
990             Yield list.ToArray
991         End If
992     End Function
993
994     ''' <summary>
995     ''' String compares using <see cref="String.Equals"/>, if the target value could not be located, 
996     ''' then -1 will be return from this function.
997     ''' </summary>
998     ''' <param name="collection"></param>
999     ''' <param name="text"></param>
1000     ''' <param name="caseSensitive"></param>
1001     ''' <param name="fuzzy">
1002     ''' If fuzzy, then <see cref="InStr"/> will be used if ``String.Equals`` method have no result.
1003     ''' </param>
1004     ''' <returns></returns>
1005     <ExportAPI("Located"Info:="String compares using String.Equals")>
1006     <Extension> Public Function Located(collection As IEnumerable(Of String),
1007                                         text As String,
1008                                         Optional caseSensitive As Boolean = True,
1009                                         Optional fuzzy As Boolean = FalseAs Integer
1010
1011         Dim method As StringComparison =
1012             If(caseSensitive,
1013             StringComparison.Ordinal,
1014             StringComparison.OrdinalIgnoreCase)
1015         Dim method2 As CompareMethod =
1016             If(caseSensitive,
1017             CompareMethod.Binary,
1018             CompareMethod.Text)
1019
1020         For Each str As SeqValue(Of StringIn collection.SeqIterator
1021             If String.Equals(str.value, text, method) Then
1022                 Return str.i
1023             ElseIf fuzzy Then
1024                 If InStr(str.value, text, method2) > 0 Then
1025                     Return str.i
1026                 End If
1027             End If
1028         Next
1029
1030         Return -1
1031     End Function
1032
1033     ''' <summary>
1034     ''' 
1035     ''' </summary>
1036     ''' <param name="collection"></param>
1037     ''' <param name="text">可以使用通配符</param>
1038     ''' <param name="caseSensitive"></param>
1039     ''' <returns></returns>
1040     <Extension>
1041     Public Function WildcardsLocated(collection As IEnumerable(Of String), text$, Optional caseSensitive As Boolean = TrueAs Integer
1042         For Each s As SeqValue(Of StringIn collection.SeqIterator
1043             If text.WildcardMatch(s.value, Not caseSensitive) Then
1044                 Return s.i
1045             End If
1046         Next
1047
1048         Return -1
1049     End Function
1050
1051     ''' <summary>
1052     ''' Search the string by keyword in a string collection. Unlike search function <see cref="StringHelpers.Located(IEnumerable(Of String), StringBooleanBoolean)"/>
1053     ''' using function <see cref="String.Equals"/> function to search string, this function using <see cref="Strings.InStr(String, String, CompareMethod)"/>
1054     ''' to search the keyword.
1055     ''' </summary>
1056     ''' <param name="source"></param>
1057     ''' <param name="keyword"></param>
1058     ''' <param name="caseSensitive"></param>
1059     ''' <returns>返回第一个找到关键词的行数,没有找到则返回-1</returns>
1060     <ExportAPI("Lookup"Info:="Search the string by keyword in a string collection.")>
1061     <Extension>
1062     Public Function Lookup(source As IEnumerable(Of String), keyword As StringOptional caseSensitive As Boolean = TrueAs Integer
1063         Dim method As CompareMethod = If(caseSensitive, CompareMethod.Binary, CompareMethod.Text)
1064         Dim i As Integer
1065
1066         For Each line As String In source
1067             If InStr(line, keyword, method) > 0 Then
1068                 Return i
1069             Else
1070                 i += 1
1071             End If
1072         Next
1073
1074         Return -1
1075     End Function
1076
1077     ''' <summary>
1078     ''' 判断目标字符串是否与字符串参数数组之中的任意一个字符串相等,大小写不敏感,假若没有相等的字符串,则会返回空字符串,假若找到了相等的字符串,则会返回该字符串
1079     ''' </summary>
1080     ''' <param name="source"></param>
1081     ''' <param name="compareTo"></param>
1082     ''' <returns></returns>
1083     <Extension>
1084     <ExportAPI("Equals.Any")>
1085     Public Function EqualsAny(source$, ParamArray compareTo As String()) As String
1086         Dim index As Integer = compareTo.Located(source, False)
1087
1088         If index = -1 Then
1089             Return ""
1090         Else
1091             Return compareTo(index)
1092         End If
1093     End Function
1094
1095     ''' <summary>
1096     ''' 查找到任意一个既返回位置,大小写不敏感,假若查找不到,则返回-1值,判断是否查找成功,可以使用 &lt;0 来完成,
1097     ''' 因为是通过InStr来完成的,所以查找成功的时候,最小的值是1,即字符串序列的第一个位置,也是元素0位置
1098     ''' </summary>
1099     ''' <param name="text"></param>
1100     ''' <param name="find"></param>
1101     ''' <returns></returns>
1102     <ExportAPI("InStr.Any")>
1103     <Extension> Public Function InStrAny(text$, ParamArray find$()) As Integer
1104         For Each token As String In find
1105             Dim idx% = Strings.InStr(text, token, CompareMethod.Text)
1106
1107             If idx > 0 Then
1108                 Return idx
1109             End If
1110         Next
1111
1112         Return -1
1113     End Function
1114
1115     ''' <summary>
1116     ''' Removes the duplicated string from the source <paramref name="strings"/> collection 
1117     ''' with string compare ignore case.
1118     ''' </summary>
1119     ''' <param name="strings"></param>
1120     ''' <returns></returns>
1121     <Extension>
1122     Public Function DistinctIgnoreCase(strings As IEnumerable(Of String)) As IEnumerable(Of String)
1123         Dim list = strings.Distinct.ToArray
1124         Dim lowerBuffers As New Dictionary(Of String, List(Of String))
1125
1126         For Each s As String In list
1127             With s.ToLower
1128                 If Not lowerBuffers.ContainsKey(.ByRef) Then
1129                     lowerBuffers(.ByRef) = New List(Of String)
1130                 End If
1131                 lowerBuffers(.ByRef).Add(s)
1132             End With
1133         Next
1134
1135         Dim distinct = lowerBuffers _
1136             .Select(Function(pack)
1137                         Dim n$() = pack _
1138                             .Value _
1139                             .Where(Function(s) s <> pack.Key) _
1140                             .ToArray
1141
1142                         ' 尽量不返回全部都是小写的字符串
1143                         If n.Length > 0 Then
1144                             Return n.First
1145                         Else
1146                             Return pack.Key
1147                         End If
1148                     End Function) _
1149             .ToArray
1150
1151         Return distinct
1152     End Function
1153
1154     ''' <summary>
1155     ''' Line tokens. **=> Parsing the text into lines by using <see cref="vbCr"/>, <see cref="vbLf"/>**.
1156     ''' (函数对文本进行分行操作,由于在Windows(<see cref="VbCrLf"/>)和
1157     ''' Linux(<see cref="vbCr"/>, <see cref="vbLf"/>)平台上面所生成的文本文件的换行符有差异,
1158     ''' 所以可以使用这个函数来进行统一的分行操作)
1159     ''' </summary>
1160     ''' <param name="s"></param>
1161     ''' <returns></returns>
1162     ''' <param name="trim">
1163     ''' Set <see cref="Boolean.FalseString"/> to avoid a reader bug in the csv data reader <see cref="BufferedStream"/>
1164     ''' </param>
1165     <ExportAPI("LineTokens")>
1166     <Extension> Public Function LineTokens(s$, Optional trim As Boolean = TrueAs String()
1167         If String.IsNullOrEmpty(s) Then
1168             Return {}
1169         End If
1170
1171         Dim lf As Boolean = InStr(s, vbLf) > 0
1172         Dim cr As Boolean = InStr(s, vbCr) > 0
1173
1174         If Not (cr OrElse lf) Then  ' 没有分行符,则只有一行
1175             Return {s}
1176         End If
1177
1178         If lf AndAlso cr Then
1179             If trim Then  ' 假若将这个换行替换掉,在Csv文件读取模块会出现bug。。。。。不清楚是怎么回事
1180                 s = s.Replace(vbCr, "")
1181             End If
1182
1183             Return Splitter.Split(s, vbLf, True)
1184         End If
1185
1186         If lf Then
1187             Return Splitter.Split(s, vbLf, True)
1188         Else
1189             Return Splitter.Split(s, vbCr, True)
1190         End If
1191     End Function
1192
1193     ''' <summary>
1194     ''' Does the <paramref name="token"/> is located at the last of <paramref name="s"/> text string.
1195     ''' </summary>
1196     ''' <param name="s$"></param>
1197     ''' <param name="token$"></param>
1198     ''' <returns></returns>
1199     <Extension> Public Function TextLast(s$, token$) As Boolean
1200         Dim lastIndex% = s.Length - token.Length
1201         ' 因为token子字符串可能会在s字符串之中出现多次,所以直接使用正向的InStr函数
1202         ' 可能会导致匹配到第一个字符串而无法正确的匹配上最后一个token,所以在这里使用
1203         InstrRev来避免这个问题
1204         Dim val% = InStrRev(s, token)
1205         Return lastIndex = val
1206     End Function
1207 End Module