1 #Region "Microsoft.VisualBasic::509ad5f16164f9699b72fcc689e57862, Microsoft.VisualBasic.Core\Extensions\StringHelpers\StrUtils.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 StrUtils
35
36 '     Properties: InvariantCulture
37
38 '     Function: AddWithDelim, CharCode, CharCodes, CharString, (+6 Overloads) ContactWithDelim
39 '               ContactWithDelimSkipEmpty, ContactWithDelimSkipNull, ContactWithDelimSkipSome, CountWordFrequency, (+2 OverloadsEndsWith
40 '               EscapeQuotesAndBackslashes, GetCompareType, GetHeader, GetLastSubStringBetween, GetString
41 '               GetSubStringBetween, GetWords, LongestTag, LowerCaseFirstChar, RandomASCII
42 '               RandomASCIIString, RandomCharString, Remove, SplitIntoLines, SplitRemoveEmptyEntries
43 '               SplitWithSeparator, SplitWithSeparatorFromRight, SplitWithSpaces, (+2 Overloads) StartsWith, StartWithUpperCase
44 '               UpperCaseFirstChar
45
46 ' /********************************************************************************/
47
48 #End Region
49
50 '
51 ' System.Web.Util.StrUtils
52 '
53 ' Author(s):
54 '  Gonzalo Paniagua Javier (gonzalo@ximian.com)
55 '
56 ' (C) 2005 Novell, Inc, (http://www.novell.com)
57 '
58
59 '
60 ' Permission is hereby granted, free of charge, to any person obtaining
61 ' a copy of this software and associated documentation files (the
62 "Software"), to deal in the Software without restriction, including
63 ' without limitation the rights to use, copy, modify, merge, publish,
64 ' distribute, sublicense, and/or sell copies of the Software, and to
65 ' permit persons to whom the Software is furnished to do so, subject to
66 ' the following conditions:
67
68 ' The above copyright notice and this permission notice shall be
69 ' included in all copies or substantial portions of the Software.
70
71 ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
72 ' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
73 ' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
74 ' NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
75 ' LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
76 ' OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
77 ' WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
78 '
79
80 Imports System.Globalization
81 Imports System.Numerics
82 Imports System.Runtime.CompilerServices
83 Imports System.Text
84 Imports System.Text.RegularExpressions
85 Imports Microsoft.VisualBasic.Language
86 Imports Microsoft.VisualBasic.Language.Default
87 Imports Microsoft.VisualBasic.Linq
88 Imports Microsoft.VisualBasic.Text
89 Imports r = System.Text.RegularExpressions.Regex
90
91 Public Module StrUtils
92
93     ' Exceptions:
94     '   T:System.ArgumentException:
95     '     A regular expression parsing error occurred.
96     '
97     '   T:System.ArgumentNullException:
98     '     input, pattern, or replacement is null.
99     '
100     '   T:System.ArgumentOutOfRangeException:
101     '     options is not a valid bitwise combination of System.Text.RegularExpressions.RegexOptions
102     '     values.
103     '
104     '   T:System.Text.RegularExpressions.RegexMatchTimeoutException:
105     '     A time-out occurred. For more information about time-outs, see the Remarks section.
106     ''' <summary>
107     ''' In a specified input string, replaces all strings that match a specified regular
108     ''' expression with a specified replacement string. Specified options modify the
109     ''' matching operation.
110     ''' </summary>
111     ''' <param name="s$">The string to search for a match.</param>
112     ''' <param name="pattern$">The regular expression pattern to match.</param>
113     ''' <param name="opts">A bitwise combination of the enumeration values that provide options for matching.</param>
114     ''' <returns>
115     ''' A new string that is identical to the input string, except that the replacement
116     ''' string takes the place of each matched string. If pattern is not matched in the
117     ''' current instance, the method returns the current instance unchanged.
118     ''' </returns>
119     ''' 
120     <MethodImpl(MethodImplOptions.AggressiveInlining)>
121     <Extension>
122     Public Function Remove(s$, pattern$, Optional opts As RegexOptions = RegexICSng) As String
123         Return r.Replace(s, pattern, "", opts)
124     End Function
125
126     <MethodImpl(MethodImplOptions.AggressiveInlining)>
127     <Extension>
128     Public Function GetCompareType(type As CompareMethod) As StringComparison
129         If type = CompareMethod.Binary Then
130             Return StringComparison.Ordinal
131         Else
132             Return StringComparison.OrdinalIgnoreCase
133         End If
134     End Function
135
136     <MethodImpl(MethodImplOptions.AggressiveInlining)>
137     <Extension>
138     Public Function GetString(bytes As IEnumerable(Of Byte), Optional encoding As Encodings = Encodings.UTF8) As String
139         Return encoding.CodePage.GetString(bytes.ToArray)
140     End Function
141
142     ''' <summary>
143     ''' 从第一个字符开始,比较出最长的label串,注意,这个不是LCS问题
144     ''' </summary>
145     ''' <param name="labels$"></param>
146     ''' <returns></returns>
147     <Extension> Public Function LongestTag(labels$()) As String
148         Dim l As New List(Of Char)
149         Dim minl% = Aggregate s In labels Into Min(s.Length)
150
151         For i As Integer = 0 To minl
152             Dim index = i
153             Dim cs = labels.Select(Function(s) s.Chars(index)).ToArray
154             Dim c = cs.First
155
156             If cs.All(Function(x) x = c) Then
157                 l += c
158             Else
159                 ' 已经有不一样的了,到终点了
160                 Exit For
161             End If
162         Next
163
164         Return New String(l)
165     End Function
166
167     ''' <summary>
168     ''' <see cref="AscW"/>
169     ''' </summary>
170     ''' <param name="s"></param>
171     ''' <returns></returns>
172     <Extension>
173     <MethodImpl(MethodImplOptions.AggressiveInlining)>
174     Public Function CharCodes(s As IEnumerable(Of Char)) As Integer()
175         Return s.SafeQuery.Select(AddressOf AscW).ToArray
176     End Function
177
178     <MethodImpl(MethodImplOptions.AggressiveInlining)>
179     <Extension>
180     Public Function CharCode(c As CharAs Integer
181         Return AscW(c)
182     End Function
183
184     ReadOnly newRandom As New DefaultValue(Of Random)(Function() New Random)
185
186     ''' <summary>
187     ''' 32-126
188     ''' </summary>
189     ''' <param name="len%"></param>
190     ''' <returns></returns>
191     Public Function RandomASCIIString(len%, Optional skipSymbols As Boolean = FalseOptional seed As Random = NothingAs String
192         With seed Or newRandom
193             Return CharString(len, Function() .RandomASCII(skipSymbols))
194         End With
195     End Function
196
197     <Extension>
198     Public Function RandomASCII(random As Random, skipSymbols As BooleanAs Char
199         With random
200             If Not skipSymbols Then
201                 Return Chr(.Next(32, 127))
202             Else
203                 ' 只有字母和数字
204                 Select Case .NextDouble
205                     Case <= 0.3
206                         ' 数字
207                         Return Chr(.Next(48, 58))
208                     Case <= 0.6
209                         ' 小写字母
210                         Return Chr(.Next(97, 123))
211                     Case Else
212                         ' 大写字母
213                         Return Chr(.Next(65, 91))
214                 End Select
215             End If
216         End With
217     End Function
218
219     <Extension>
220     Public Function RandomCharString(chars As IEnumerable(Of Char), len%) As String
221         With New Random
222             Dim buffer = chars.ToArray
223             Return CharString(len, Function() .Next(buffer))
224         End With
225     End Function
226
227     Public Function CharString(len%, getChar As Func(Of Char)) As String
228         Dim s As New List(Of Char)
229
230         For i As Integer = 0 To len - 1
231             s.Add(getChar())
232         Next
233
234         Return New String(s.ToArray)
235     End Function
236
237     ''' <summary>
238     ''' <see cref="CultureInfo.InvariantCulture"/>, Gets the System.Globalization.CultureInfo object that is culture-independent
239     ''' (invariant).
240     ''' </summary>
241     ''' <returns></returns>
242     Public ReadOnly Property InvariantCulture As CultureInfo = CultureInfo.InvariantCulture
243
244     <MethodImpl(MethodImplOptions.AggressiveInlining)>
245     Public Function StartsWith(str1 As String, str2 As StringAs Boolean
246         Return StartsWith(str1, str2, False)
247     End Function
248
249     Public Function StartsWith(str1 As String, str2 As String, ignore_case As BooleanAs Boolean
250         Dim l2 As Integer = str2.Length
251         If l2 = 0 Then
252             Return True
253         End If
254
255         Dim l1 As Integer = str1.Length
256         If l2 > l1 Then
257             Return False
258         End If
259
260         Return (0 = String.Compare(str1, 0, str2, 0, l2, ignore_case, StrUtils.InvariantCulture))
261     End Function
262
263     <MethodImpl(MethodImplOptions.AggressiveInlining)>
264     Public Function EndsWith(str1 As String, str2 As StringAs Boolean
265         Return EndsWith(str1, str2, False)
266     End Function
267
268     Public Function EndsWith(str1 As String, str2 As String, ignore_case As BooleanAs Boolean
269         Dim l2 As Integer = str2.Length
270         If l2 = 0 Then
271             Return True
272         End If
273
274         Dim l1 As Integer = str1.Length
275         If l2 > l1 Then
276             Return False
277         End If
278
279         Return (0 = String.Compare(str1, l1 - l2, str2, 0, l2, ignore_case, StrUtils.InvariantCulture))
280     End Function
281
282     Public Function EscapeQuotesAndBackslashes(attributeValue As StringAs String
283         Dim sb As StringBuilder = Nothing
284         For i As Integer = 0 To attributeValue.Length - 1
285             Dim ch As Char = attributeValue(i)
286             If ch = "'"OrElse ch = """"OrElse ch = "\"Then
287                 If sb Is Nothing Then
288                     sb = New StringBuilder()
289                     sb.Append(attributeValue.Substring(0, i))
290                 End If
291                 sb.Append("\"c)
292                 sb.Append(ch)
293             Else
294                 If sb IsNot Nothing Then
295                     sb.Append(ch)
296                 End If
297             End If
298         Next
299         If sb IsNot Nothing Then
300             Return sb.ToString()
301         End If
302         Return attributeValue
303     End Function
304
305     <MethodImpl(MethodImplOptions.AggressiveInlining)>
306     Public Function SplitRemoveEmptyEntries(value As String, separator As Char()) As String()
307         Return value.Split(separator, StringSplitOptions.RemoveEmptyEntries)
308     End Function
309
310     ''' <summary>
311     ''' Split text with a separator char
312     ''' </summary>
313     ''' <param name="text">The text.</param>
314     ''' <param name="sep">The separator.</param>
315     ''' <returns></returns>
316     Public Function SplitWithSeparator(text As String, sep As CharAs String()
317         If String.IsNullOrEmpty(text) Then
318             Return Nothing
319         End If
320
321         Dim items As String() = New String(1) {}
322         Dim index As Integer = text.IndexOf(sep)
323
324         If index >= 0 Then
325             items(0) = text.Substring(0, index)
326             items(1) = text.Substring(index + 1)
327         Else
328             items(0) = text
329             items(1) = Nothing
330         End If
331
332         Return items
333     End Function
334
335     ''' <summary>
336     ''' Split text with a separator char
337     ''' </summary>
338     ''' <param name="text">The text.</param>
339     ''' <param name="sep">The separator.</param>
340     ''' <returns></returns>
341     Public Function SplitWithSeparatorFromRight(text As String, sep As CharAs String()
342         If String.IsNullOrEmpty(text) Then
343             Return Nothing
344         End If
345
346         Dim items As String() = New String(1) {}
347
348         Dim index As Integer = text.LastIndexOf(sep)
349         If index >= 0 Then
350             items(0) = text.Substring(0, index)
351             items(1) = text.Substring(index + 1)
352         Else
353             items(0) = text
354             items(1) = Nothing
355         End If
356
357         Return items
358     End Function
359
360     ''' <summary>
361     ''' Splits the text with spaces.
362     ''' </summary>
363     ''' <param name="text">The text.</param>
364     ''' <returns></returns>
365     Public Function SplitWithSpaces(text As StringAs String()
366         Dim pattern As String = "[^ ]+"
367         Dim rgx As New Regex(pattern)
368         Dim mc As MatchCollection = rgx.Matches(text)
369         Dim items As String() = New String(mc.Count - 1) {}
370         For i As Integer = 0 To items.Length - 1
371             items(i) = mc(i).Value
372         Next
373         Return items
374     End Function
375
376     ''' <summary>
377     ''' Splits the text into lines.
378     ''' </summary>
379     ''' <param name="text">The text.</param>
380     ''' <returns></returns>
381     Public Function SplitIntoLines(text As StringAs String()
382         Dim lines As New List(Of String)()
383         Dim line As New StringBuilder()
384         For Each ch As Char In text
385             Select Case ch
386                 Case ControlChars.Cr
387
388                 Case ControlChars.Lf
389                     lines.Add(line.ToString())
390                     line.Length = 0
391
392                 Case Else
393                     line.Append(ch)
394
395             End Select
396         Next
397         If line.Length > 0 Then
398             lines.Add(line.ToString())
399         End If
400         Return lines.ToArray()
401     End Function
402
403     ''' <summary>
404     ''' Concats two strings with a delimiter.
405     ''' </summary>
406     ''' <param name="s1">string 1</param>
407     ''' <param name="delim">delimiter</param>
408     ''' <param name="s2">string 2</param>
409     ''' <returns></returns>
410     Public Function AddWithDelim(s1 As String, delim As String, s2 As StringAs String
411         If String.IsNullOrEmpty(s1) Then
412             Return s2
413         Else
414             Return s1 & delim & s2
415         End If
416     End Function
417     ''' <summary>
418     ''' Contacts the with delim.
419     ''' </summary>
420     ''' <param name="str1">The STR1.</param>
421     ''' <param name="delim">The delim.</param>
422     ''' <param name="str2">The STR2.</param>
423     ''' <returns></returns>
424     Public Function ContactWithDelim(str1 As String, delim As String, str2 As StringAs String
425         If String.IsNullOrEmpty(str1) Then
426             Return str2
427         ElseIf String.IsNullOrEmpty(str2) Then
428             Return str1
429         Else
430             Return str1 & delim & str2
431         End If
432     End Function
433
434     ''' <summary>
435     ''' Contact with delim, delim is used after the first not Empty item
436     ''' </summary>
437     ''' <param name="items"></param>
438     ''' <param name="delim"></param>
439     ''' <returns></returns>
440     Public Function ContactWithDelimSkipEmpty(items As IEnumerable(Of String), delim As StringAs String
441         Return ContactWithDelimSkipSome(items, delim, String.Empty)
442     End Function
443
444     ''' <summary>
445     ''' Contact with delim, delim is used after the first not null item
446     ''' </summary>
447     ''' <param name="items"></param>
448     ''' <param name="delim"></param>
449     ''' <returns></returns>
450     Public Function ContactWithDelimSkipNull(items As IEnumerable(Of String), delim As StringAs String
451         Return ContactWithDelimSkipSome(items, delim, Nothing)
452     End Function
453
454     ''' <summary>
455     ''' Contacts the items with delim skip some.
456     ''' </summary>
457     ''' <param name="items">The items.</param>
458     ''' <param name="delim">The delim.</param>
459     ''' <param name="skip">The skip.</param>
460     ''' <returns></returns>
461     Public Function ContactWithDelimSkipSome(items As IEnumerable(Of String), delim As String, skip As StringAs String
462         Dim text As New StringBuilder()
463         Dim isFirst As Boolean = True
464         For Each item As String In items
465             If item = skip Then
466                 Continue For
467             End If
468             If isFirst Then
469                 isFirst = False
470             Else
471                 text.Append(delim)
472             End If
473             text.Append(item)
474         Next
475         Return text.ToString()
476     End Function
477
478     ''' <summary>
479     ''' Contacts the items with delim.
480     ''' </summary>
481     ''' <param name="items">The items.</param>
482     ''' <param name="delim">The delim.</param>
483     ''' <returns></returns>
484     Public Function ContactWithDelim(items As IEnumerable(Of String), delim As StringAs String
485         Return ContactWithDelim(items, delim, NothingNothing)
486     End Function
487     ''' <summary>
488     ''' Contacts the items with delim.
489     ''' </summary>
490     ''' <param name="items">The items.</param>
491     ''' <param name="delim">The delim.</param>
492     ''' <param name="initialValue">The initial value.</param>
493     ''' <returns></returns>
494     Public Function ContactWithDelim(items As IEnumerable(Of String), delim As String, initialValue As StringAs String
495         Return ContactWithDelim(items, delim, initialValue, Nothing)
496     End Function
497     ''' <summary>
498     ''' Contacts the items with delim.
499     ''' </summary>
500     ''' <param name="items">The items.</param>
501     ''' <param name="delim">The delim.</param>
502     ''' <param name="initialValue">The initial value.</param>
503     ''' <param name="endValue">The end value.</param>
504     ''' <returns></returns>
505     Public Function ContactWithDelim(items As IEnumerable(Of String), delim As String, initialValue As String, endValue As StringAs String
506         Dim text As New StringBuilder(initialValue)
507         Dim isFirst As Boolean = True
508         For Each item As String In items
509             If isFirst Then
510                 isFirst = False
511             Else
512                 text.Append(delim)
513             End If
514             text.Append(item)
515         Next
516         text.Append(endValue)
517         Return text.ToString()
518     End Function
519
520     ''' <summary>
521     ''' Contacts the items with delim.
522     ''' </summary>
523     ''' <typeparam name="T"></typeparam>
524     ''' <param name="items">The items.</param>
525     ''' <param name="delim">The delim.</param>
526     ''' <returns></returns>
527     Public Function ContactWithDelim(Of T)(items As IEnumerable(Of T), delim As StringAs String
528         Return ContactWithDelim(Of T)(items, delim, NothingNothing)
529     End Function
530
531     ''' <summary>
532     ''' Contacts the items with delim.
533     ''' </summary>
534     ''' <typeparam name="T"></typeparam>
535     ''' <param name="items">The items.</param>
536     ''' <param name="delim">The delim.</param>
537     ''' <param name="initialValue">The initial value.</param>
538     ''' <param name="endValue">The end value.</param>
539     ''' <returns></returns>
540     Public Function ContactWithDelim(Of T)(items As IEnumerable(Of T), delim As String, initialValue As String, endValue As StringAs String
541         Dim text As New StringBuilder()
542         text.Append(initialValue)
543         Dim isFirst As Boolean = True
544         For Each item As T In items
545             If isFirst Then
546                 isFirst = False
547             Else
548                 text.Append(delim)
549             End If
550             text.Append(item.ToString())
551         Next
552         text.Append(endValue)
553         Return text.ToString()
554     End Function
555
556     ''' <summary>
557     ''' Gets the header.
558     ''' </summary>
559     ''' <param name="text">The text.</param>
560     ''' <param name="length">The length.</param>
561     ''' <returns></returns>
562     Public Function GetHeader(text As String, length As IntegerAs String
563         If text.Length <= length Then
564             Return text
565         Else
566             Return text.Substring(0, length)
567         End If
568     End Function
569
570     ''' <summary>
571     ''' Get the sub string between 'ket' and 'bra'.
572     ''' </summary>
573     ''' <param name="text"></param>
574     ''' <param name="bra"></param>
575     ''' <param name="ket"></param>
576     ''' <returns></returns>
577     Public Function GetSubStringBetween(text As String, bra As Char, ket As CharAs String
578         If text Is Nothing Then
579             Return Nothing
580         End If
581         Dim braIndex As Integer = text.IndexOf(bra)
582         If braIndex > -1 Then
583             Dim ketIndex As Integer = text.IndexOf(ket)
584             If ketIndex > braIndex Then
585                 Return text.Substring(braIndex + 1, ketIndex - braIndex - 1)
586             End If
587         End If
588         Return String.Empty
589     End Function
590
591     ''' <summary>
592     ''' Get the sub string between 'ket' and 'bra'.
593     ''' </summary>
594     ''' <param name="text"></param>
595     ''' <param name="bra"></param>
596     ''' <param name="ket"></param>
597     ''' <returns></returns>
598     Public Function GetLastSubStringBetween(text As String, bra As Char, ket As CharAs String
599         If text Is Nothing Then
600             Return Nothing
601         End If
602         Dim braIndex As Integer = text.LastIndexOf(bra)
603         If braIndex > -1 Then
604             Dim ketIndex As Integer = text.LastIndexOf(ket)
605             If ketIndex > braIndex Then
606                 Return text.Substring(braIndex + 1, ketIndex - braIndex - 1)
607             End If
608         End If
609         Return String.Empty
610     End Function
611
612     ''' <summary>
613     ''' Starts with upper case.
614     ''' </summary>
615     ''' <param name="name">The name.</param>
616     ''' <returns></returns>
617     <MethodImpl(MethodImplOptions.AggressiveInlining)>
618     <Extension>
619     Public Function StartWithUpperCase(name As StringAs Boolean
620         Return name.Length >= 1 AndAlso Char.IsUpper(name(0))
621     End Function
622
623     ''' <summary>
624     ''' Uppers the case of the first char.
625     ''' </summary>
626     ''' <param name="name">The name.</param>
627     ''' <returns></returns>
628     <Extension>
629     Public Function UpperCaseFirstChar(name As StringAs String
630         If name.Length >= 1 AndAlso Char.IsLower(name(0)) Then
631             Dim chars As Char() = name.ToCharArray()
632             chars(0) = [Char].ToUpper(chars(0))
633             Return New String(chars)
634         End If
635
636         Return name
637     End Function
638
639     ''' <summary>
640     ''' Lowers the case of the first char.
641     ''' </summary>
642     ''' <param name="name">The name.</param>
643     ''' <returns></returns>
644     Public Function LowerCaseFirstChar(name As StringAs String
645         If name.Length >= 1 AndAlso Char.IsUpper(name(0)) Then
646             Dim chars As Char() = name.ToCharArray()
647             chars(0) = [Char].ToLower(chars(0))
648             Return New String(chars)
649         End If
650         Return name
651     End Function
652
653     ''' <summary>
654     ''' split text into words by space and newline chars, multiple spaces are treated as a single space.
655     ''' </summary>
656     ''' <param name="text"></param>
657     ''' <returns></returns>
658     Public Function GetWords(text As StringAs String()
659         Dim tokens As New List(Of String)()
660         Dim token As New List(Of Char)()
661
662         For Each ch As Char In text
663             Select Case ch
664                 Case " "c, ControlChars.Cr, ControlChars.Lf
665                     If token.Count > 0 Then
666                         tokens.Add(New String(token.ToArray()))
667                         token.Clear()
668                     End If
669                 Case Else
670                     token.Add(ch)
671             End Select
672         Next
673
674         If token.Count > 0 Then
675             tokens.Add(New String(token.ToArray()))
676         End If
677
678         Return tokens.ToArray()
679     End Function
680
681     Public Function CountWordFrequency(article As String, delimiters As StringAs Dictionary(Of StringInteger)
682         If article Is Nothing Then
683             Throw New ArgumentNullException("article")
684         End If
685         If delimiters Is Nothing Then
686             Throw New ArgumentNullException("delimiters")
687         End If
688         Dim words As New List(Of String)()
689         Dim buffer As New List(Of Char)()
690         For Each c As Char In article
691             If delimiters.IndexOf(c) = -1 Then
692                 buffer.Add(c)
693             Else
694                 If buffer.Count > 0 Then
695                     words.Add(New String(buffer.ToArray()))
696                     buffer.Clear()
697                 End If
698             End If
699         Next
700         If buffer.Count > 0 Then
701             words.Add(New String(buffer.ToArray()))
702         End If
703
704         Dim table As New Dictionary(Of StringInteger)()
705         For Each word As String In words
706             If table.ContainsKey(word) Then
707                 table(word) += 1
708             Else
709                 table.Add(word, 1)
710             End If
711         Next
712         Return table
713     End Function
714 End Module