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