This repository has been archived by the owner on Nov 27, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
EvilWorks.System.StrUtils.pas
1739 lines (1539 loc) · 47.4 KB
/
EvilWorks.System.StrUtils.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// EvilLibrary by Vedran Vuk 2010-2012
//
// Name: EvilWorks.System.StrUtils
// Description: A collection of pure pascal string parsing functions! :)
// And still ~100x faster than Python, PHP, javascript... :P
// File last change date: October 30th. 2012
// File version: Dev 0.0.0
// Licence: Free.
//
unit EvilWorks.System.StrUtils;
interface
uses
System.SysUtils,
System.StrUtils;
{$I EvilWorks.System.StrUtils.inc}
type
{ Forward declarations }
TTokensEnumerator = class;
{ TSplitOption }
{ Options for TextSplit(), TextTokenize() }
TSplitOption = (
soNoDelSep, // Tokens will be added to list along with their trailing separators.
soCSSep, // Token separators are treated as Case Sensitive. SPEEDS UP! parsing.
soCSQot, // Quote character/string is treated as Case Sensitive. SPEEDS UP! parsing if [soQuoted].
soSingleSep, // Splitting will stop after the first separator; Two tokens total.
soQuoted, // Treat strings quoted/enclosed in Quote as single token.
soRemQuotes // Remove Quote from parsed out tokens.
);
TSplitOptions = set of TSplitOption;
{ TPair }
{ Your standard Key=Value pair record. }
TPair = record
Key: string;
Val: string;
end;
{ TTokens }
{ A helpful text array container for all sorts of formatting and parsing. }
{ Can be declared as standalone (initialize with Clear), or returned by TextTokenize(). }
TTokens = record
private
FTokens: TArray<string>;
FCount : Integer;
function GetToken(const aIndex: Integer): string;
procedure QuickSort(aStart, aEnd: Integer);
function GetPair(const aIndex: integer): TPair;
public
function GetEnumerator: TTokensEnumerator;
function FromToken(const aIndex: Integer; const aDelimiter: string = CSpace): string;
function ToToken(const aIndex: Integer; const aDelimiter: string = CSpace): string;
function AllTokens(const aDelimiter: string = CSpace): string;
procedure Add(const aText: string); overload;
procedure Add(const aKey, aVal: string); overload;
procedure Add(const aKey: string; const aVal: integer); overload;
procedure Add(const aKey: string; const aVal: boolean); overload;
procedure AddQ(const aKey, aVal: string); overload;
procedure AddQ(const aKey: string; const aVal: integer); overload;
procedure AddQ(const aKey: string; const aVal: boolean); overload;
procedure Exchange(aIndexA, aIndexB: Integer);
procedure Sort;
procedure Clear;
function ToArray(const aFromToken: integer = 0; const aToToken: integer = maxint): TArray<string>; overload;
property Token[const aIndex: Integer]: string read GetToken; default;
property Pair[const aIndex: integer]: TPair read GetPair;
property Count: Integer read FCount;
function Empty: boolean;
end;
{ TTokensEnumerator }
{ Enumerator for TTokens. }
TTokensEnumerator = class
private
FIndex : integer;
FTokens: TTokens;
public
constructor Create(aTokens: TTokens);
function GetCurrent: string; inline;
function MoveNext: Boolean; inline;
property Current: string read GetCurrent;
end;
{ Basic string handling }
function TextPos(const aText, aSubText: string; const aCaseSens: boolean = False; const aOfs: Integer = 1): Integer;
function TextCopy(const aText: string; const aStartIdx, aCount: Integer): string;
function TextUpCase(const aText: string): string;
function TextLoCase(const aText: string): string;
function TextReplace(const aText, aSubText, aNewText: string; const aCaseSens: boolean = False): string;
procedure TextAppend(var aText: string; const aAppendWith: string);
{ More exotic functions of basic variety }
procedure TextAppendWithFeed(var aText: string; const aAppendWith: string);
procedure TextKeyValueAppend(var aOutStr: string; const aKey, aValue: string; const aAnd: boolean = True);
function TextEscStr(const aText, aEscape: string): string;
{ Comparison, extraction, splitting, tokenizing... }
function TextLeft(const aText: string; const aCount: Integer): string;
function TextRight(const aText: string; const aCount: Integer): string;
function TextBegins(const aText, aBeginsWith: string; aCaseSens: boolean = False): boolean;
function TextEnds(const aText, aEndsWith: string; aCaseSens: boolean = False): boolean;
function TextSame(const aTextA, aTextB: string; const aCaseSens: boolean = False): boolean; inline;
function TextEquals(const aTextA, aTextB: string; const aCaseSens: boolean = False): boolean;
function TextInText(const aText, aContainsText: string; const aCaseSens: boolean = False): boolean;
function TextInArray(const aText: string; const aArray: array of string; const aAnywhere: boolean = True; const aCaseSens: boolean = False): boolean;
function TextWildcard(const aText, aWildCard: string): boolean;
function TextEnclosed(const aText, aLeftSide, aRightSide: string; const aCaseSens: boolean = False): boolean; overload;
function TextEnclosed(const aText, aEnclosedWith: string; const aCaseSens: boolean = False): boolean; overload;
function TextEnclose(const aText, aEncloseWith: string): string;
function TextUnEnclose(const aText, aEnclosedWith: string; const aCaseSens: boolean = False): string; overload;
function TextUnEnclose(const aText, aLeftSide, aRightSide: string; const aCaseSens: boolean = False): string; overload;
function TextFindEnclosed(const aText, aEnclLeft, aEnclRight: string; const aIdx: Integer; const aRemEncl: boolean = True; const aCaseSens: boolean = False): string; overload;
function TextFindEnclosed(const aText, aEncl: string; const aIdx: Integer; const aRemEncl: boolean = True; const aCaseSens: boolean = False): string; overload;
function TextQuote(const aText: string): string;
function TextUnquote(const aText: string): string;
function TextRemoveLineFeeds(const aText: string): string;
function TextExtractLeft(var aText: string; const aSep: string; const aCaseSens: boolean = False; const aDelSep: boolean = True): string;
function TextExtractRight(var aText: string; const aSep: string; const aCaseSens: boolean = False; const aDelSep: boolean = True): string;
function TextFetchLeft(const aText, aSep: string; const aCaseSens: boolean = False; const aEmptyIfNoSep: boolean = True): string;
function TextFetchRight(const aText, aSep: string; const aCaseSens: boolean = False; const aEmptyIfNoSep: boolean = True; const aSepFromRight: boolean = True): string;
function TextFetchLine(const aText: string): string;
function TextRemoveLeft(const aText, aRemove: string; const aCaseSens: boolean = False): string;
function TextRemoveRight(const aText, aRemove: string; const aCaseSens: boolean = False): string;
function TextSplit(const aText: string; const aSep: string = CSpace; const aQotStr: string = CDoubleQuote; const aOptions: TSplitOptions = [soCSSep, soCSQot]): TArray<string>;
function TextSplitMarkup(const aText: string; const aTrim: boolean = True): TArray<string>;
function TextTokenize(const aText: string; const aSep: string = CSpace; const aQotStr: string = CDoubleQuote; const aOptions: TSplitOptions = [soCSSep, soCSQot]): TTokens;
function TextToken(const aText: string; const aIndex: integer; const aSeparator: string = CSpace): string;
{ Conversion and formating rotines }
function TextToInt(const aText: string; const aDefault: Integer): Integer;
function TextFromBool(const aBoolean: boolean; const aUseBoolStrings: boolean = True): string;
function TextFromInt(const aByte: byte): string; overload;
function TextFromInt(const aInteger: integer): string; overload;
function TextFromInt(const aCardinal: cardinal): string; overload;
function TextFromInt(const aInt64: int64): string; overload;
function TextFromFloat(const aFloat: double; const aDecimals: byte = 6): string; overload;
function TextFromFloat(const aExtended: extended; const aDecimals: byte = 6): string; overload;
function TextHexToDec(const aHexStr: string): cardinal;
function TextIntToHex(const aValue, aDigits: integer): string;
function TextMake(const aArgs: array of const; const aSeparator: string = ' '): string;
{ URI text related functions }
function TextURISplit(const aURI: string; var aPrefix, aHost, aPath: string): boolean; overload;
function TextURISplit(const aURI: string; var aPrefix, aHost, aPath, aParams: string): boolean; overload;
function TextURIGetPath(const aURI: string): string;
function TextURIExtractParams(const aURI: string): string;
function TextURIWithoutParams(const aURI: string): string;
{ Various utility functions }
function TextDump(const aData: pByte; const aSize: integer; const aBytesPerLine: byte = 16): string;
procedure TextSave(const aText, aFileName: string);
function TextOfChar(const aChar: char; const aLength: integer): string;
{ IRC related functions }
function SplitHostMask(const aHostMask: string; var aNickname, aIdent, aHost: string): boolean;
{ Random string generation functions }
function RandomNum: char;
function RandomNums(const aLength: byte): string;
function RandomAlphaLower: char;
function RandomAlphaLowers(const aLength: byte): string;
function RandomAlphaUpper: char;
function RandomAlphaUppers(const aLength: byte): string;
function RandomVowelLower: char;
function RandomVowelUpper: char;
function RandomVowel: char;
function RandomConsonantLower: char;
function RandomConsonantUpper: char;
function RandomConsonant: char;
function RandomString(const aLength: Integer; const aLowerCase, aUpperCase, aNumeric: boolean): string; overload;
type
TTextHelper = record helper for string
public
function Len: integer;
function Size: integer; inline;
function Pos(const aSubText: string; const aCaseSens: boolean = False; const aOfs: Integer = 1): Integer;
function Copy(const aStartIdx, aCount: Integer): string;
function UpCase: string;
function LoCase: string;
function Replace(const aSubText, aNewText: string; const aCaseSens: boolean = False): string;
function Append(const aAppendWith: string; const aOnlyIfNotExists: boolean = False;
const aCaseSensitive: boolean = False): string;
function Left(const aLen: integer): string;
function Right(const aLen: integer): string;
function Begins(const aWith: string; const aCaseSensitive: boolean = False): boolean;
function Ends(const aWith: string; const aCaseSensitive: boolean = False): boolean;
function Equals(const aToText: string; const aCaseSensitive: boolean = False): boolean;
function ToInt(const aDefault: integer = -1): integer;
end;
implementation
{ TTextHelper }
{ Length of Self in characters. }
function TTextHelper.Len: integer;
begin
Result := Length(Self);
end;
{ Size of Self in memory. Len * Self element size. }
function TTextHelper.Size: integer;
begin
Result := (Self.Len * StringElementSize(Self));
end;
{ Convert to int. Return aDefault on fail. }
function TTextHelper.ToInt(const aDefault: integer): integer;
var
code: Integer;
begin
Val(Self, Result, code);
if (code <> 0) then
Result := aDefault;
end;
{ Get position of substring aSubText. 0 if not found. aOfs sets search start offset. }
function TTextHelper.Pos(const aSubText: string; const aCaseSens: boolean; const aOfs: Integer): Integer;
begin
Result := EvilWorks.System.StrUtils.TextPos(Self, aSubText, acaseSens, aOfs);
end;
{ Copy part of the string defined with aStartIdx and aCount. Returns nothing on invalid params or empty. }
function TTextHelper.Copy(const aStartIdx, aCount: Integer): string;
begin
Result := EvilWorks.System.StrUtils.TextCopy(Self, aStartIdx, aCount);
end;
{ Return UPPERCASE formatted self. }
function TTextHelper.UpCase: string;
begin
Result := EvilWorks.System.StrUtils.TextUpCase(Self);
end;
{ Return lowercase formatted self. }
function TTextHelper.LoCase: string;
begin
Result := EvilWorks.System.StrUtils.TextLoCase(Self);
end;
{ Replace aSubText in Self with aNewText, return result. aCaseSens sets case sensitivity of the search. }
{ If aSubText is not found Self is just copied to result. }
function TTextHelper.Replace(const aSubText, aNewText: string; const aCaseSens: boolean): string;
begin
Result := EvilWorks.System.StrUtils.TextReplace(Self, aSubText, aNewText, aCaseSens);
end;
{ Append self with aAppendWith. If aOnlyIfExists will be appended only if Self is not already suffixed. }
{ aCaseSensitive sets case sensitivity of existing aAppendWith suffix. }
function TTextHelper.Append(const aAppendWith: string; const aOnlyIfNotExists: boolean;
const aCaseSensitive: boolean): string;
begin
if (aOnlyIfNotExists) then
if (TextEquals(Self.Right(aAppendWith.Len), aAppendWith, aCaseSensitive)) then
Exit(Self);
Result := Self + aAppendWith;
end;
{ Return aLen chars from left of Self. If aLen > than Self.Len, just return all there is. }
function TTextHelper.Left(const aLen: integer): string;
begin
Result := TextCopy(Self, 1, aLen);
end;
{ Return aLen chars from right of Self. If aLen > than Self.Len, just return all there is. }
function TTextHelper.Right(const aLen: integer): string;
begin
Result := Self.Copy(Self.Len - aLen + 1, aLen);
end;
{ Checks if Self begins with aWith. aCaseSensitive sets search case sensitivity. }
function TTextHelper.Begins(const aWith: string; const aCaseSensitive: boolean): boolean;
begin
Result := TextEquals(Self.Left(aWith.Len), aWith, aCaseSensitive);
end;
{ Checks if Self ends with aWith. aCaseSensitive sets search case sensitivity. }
function TTextHelper.Ends(const aWith: string; const aCaseSensitive: boolean): boolean;
begin
Result := TextEquals(Self.Right(aWith.Len), aWith, aCaseSensitive);
end;
{ Checks if Self equals aToText. aCaseSensitive sets search case sensitivity. }
function TTextHelper.Equals(const aToText: string; const aCaseSensitive: boolean): boolean;
begin
if (aCaseSensitive) then
Result := (Self = aToText)
else
Result := SameText(Self, aToText);
end;
{ Combines Pos and PosEx. }
function TextPos(const aText, aSubText: string; const aCaseSens: boolean = False; const aOfs: Integer = 1): Integer;
begin
if (aCaseSens = False) then
Result := PosEx(LowerCase(aSubText), LowerCase(aText), aOfs)
else
Result := PosEx(aSubText, aText, aOfs);
end;
{ Safe Copy. Won't go apeshit if aStartIdx is > Length(aText), instead it just returns empty string. }
function TextCopy(const aText: string; const aStartIdx, aCount: Integer): string;
begin
{ Safe Copy. Won't go apeshit if aStartIdx is > Length(aText). }
if (aStartIdx > Length(aText)) then
Exit(CEmpty);
Result := Copy(aText, aStartIdx, aCount);
end;
{ Uppercase }
function TextUpCase(const aText: string): string;
var
i: integer;
begin
SetLength(Result, Length(aText));
for i := 0 to Length(aText) - 1 do
Result[1] := UpCase(aText[i]);
end;
{ Lowercase }
function TextLoCase(const aText: string): string;
begin
Result := LowerCase(aText);
end;
{ Replaces all occurances of aSubText with aNewText in aText. }
function TextReplace(const aText, aSubText, aNewText: string; const aCaseSens: boolean = False): string;
var
i: Integer;
j: Integer;
begin
Result := CEmpty;
if (aText = CEmpty) then
Exit;
j := 1;
while (True) do
begin
i := TextPos(aText, aSubText, aCaseSens, j);
if (i > 0) then
begin
Result := Result + TextCopy(aText, j, i - j) + aNewText;
i := i + Length(aSubText);
j := i;
end
else
begin
Result := Result + TextRight(aText, Length(aText) - j + 1);
Exit;
end;
end;
end;
{ Append aText with aAppendWith }
procedure TextAppend(var aText: string; const aAppendWith: string);
begin
aText := aText + aAppendWith;
end;
{ Append aText with aAppendWith and CRLF. }
procedure TextAppendWithFeed(var aText: string; const aAppendWith: string);
begin
aText := aText + aAppendWith + CCrLf;
end;
{ Append aKey="aValue" pair to aOutStr and add ', ' if aAnd: aKey="aValue", }
procedure TextKeyValueAppend(var aOutStr: string; const aKey, aValue: string; const aAnd: boolean = True);
begin
if (aAnd) then
aOutStr := aOutStr + aKey + '="' + aValue + '", '
else
aOutStr := aOutStr + aKey + '="' + aValue + '"';
end;
{ Escape/replace all %s tokens in aText with aEscape}
function TextEscStr(const aText, aEscape: string): string;
begin
Result := TextReplace(aText, '%s', aEscape, False);
end;
{ Copies aCount chars from Left of aText. }
function TextLeft(const aText: string; const aCount: Integer): string;
begin
Result := TextCopy(aText, 1, aCount);
end;
{ Copies aCount chars from Right of aText. }
function TextRight(const aText: string; const aCount: Integer): string;
begin
Result := TextCopy(aText, Length(aText) - aCount + 1, aCount);
end;
{ Checks if aText begins with aBeginsWith. }
function TextBegins(const aText, aBeginsWith: string; aCaseSens: boolean = False): boolean;
begin
if (aCaseSens) then
Result := (TextLeft(aText, Length(aBeginsWith)) = aBeginsWith)
else
Result := (SameText(TextLeft(aText, Length(aBeginsWith)), aBeginsWith));
end;
{ Checks if aText ends with aEndsWith. }
function TextEnds(const aText, aEndsWith: string; aCaseSens: boolean = False): boolean;
begin
if (aCaseSens) then
Result := (TextRight(aText, Length(aEndsWith)) = aEndsWith)
else
Result := (SameText(TextRight(aText, Length(aEndsWith)), aEndsWith));
end;
{ Checks if aTextA is same as aTextB. Alias for TextEquals. }
function TextSame(const aTextA, aTextB: string; const aCaseSens: boolean): boolean; inline;
begin
Result := TextEquals(aTextA, aTextB, aCaseSens);
end;
{ Checks if aTextA is same as aTextB. }
function TextEquals(const aTextA, aTextB: string; const aCaseSens: boolean): boolean;
begin
if (aCaseSens) then
Result := (aTextA = aTextB)
else
Result := SameText(aTextA, aTextB);
end;
{ Checks if aText contains aContainsText. }
function TextInText(const aText, aContainsText: string; const aCaseSens: boolean): boolean;
begin
Result := (TextPos(aText, aContainsText, aCaseSens) <> 0);
end;
{ Checks if aText matches any entries in aArray. If aAnywhere, aText matches anywhere in an aArray item. }
function TextInArray(const aText: string; const aArray: array of string; const aAnywhere: boolean; const aCaseSens: boolean): boolean;
var
i: integer;
begin
Result := False;
for i := 0 to high(aArray) do
begin
if (aAnywhere) then
begin
if (TextInText(aArray[i], aText, aCaseSens)) then
Exit(True);
end
else
begin
if (TextEquals(aArray[i], aText, aCaseSens)) then
Exit(True);
end;
end;
end;
{ Matches aText agains aWildCard. Case insensitive. * and ? supported. For IRC. }
function TextWildcard(const aText, aWildCard: string): boolean;
var
ps: pchar;
pw: pchar;
mp: pchar;
cp: pchar;
begin
if (aText = '') or (aWildCard = '') then
Exit(False);
ps := @aText[1];
pw := @aWildCard[1];
mp := nil;
cp := nil;
while ((ps^ <> #0) and (pw^ <> CAsterisk)) do
begin
if ((pw^ <> CQuestionMark) and (SameText(ps^, pw^) = False)) then
Exit(False);
Inc(ps);
Inc(pw);
end;
while (ps^ <> #0) do
begin
if (pw^ = CAsterisk) then
begin
Inc(pw);
if (pw^ = #0) then
Exit(True);
mp := pw;
cp := @ps[1];
end
else
begin
if (SameText(ps^, pw^)) or (pw^ = CQuestionMark) then
begin
Inc(ps);
Inc(pw);
end
else
begin
ps := cp;
Inc(cp);
pw := mp;
end;
end;
end;
while (pw^ = CAsterisk) do
Inc(pw);
Result := (pw^ = #0);
end;
{ Checks if left of aText is prefixed with aLeftSide and right of aText is suffixed with aRightSide. }
function TextEnclosed(const aText, aLeftSide, aRightSide: string; const aCaseSens: boolean = False): boolean;
begin
if (aCaseSens) then
Result := ((TextLeft(aText, Length(aLeftSide)) = aLeftSide) and (TextRight(aText, Length(aRightSide)) = aRightSide))
else
Result := (SameText(TextLeft(aText, Length(aLeftSide)), aLeftSide) and SameText(TextRight(aText, Length(aRightSide)), aRightSide));
end;
{ Checks if aText is prefixed and suffixed with aEnclosedWith. e.g. xXxTeenageDawgxXx }
function TextEnclosed(const aText, aEnclosedWith: string; const aCaseSens: boolean = False): boolean;
begin
Result := TextEnclosed(aText, aEnclosedWith, aEnclosedWith, aCaseSens);
end;
{ Encloses a aText within aEncloseWith. }
function TextEnclose(const aText, aEncloseWith: string): string;
begin
Result := aEncloseWith + aText + aEncloseWith;
end;
{ Removes aEnclosedWith prefix AND/OR suffix from aText. }
function TextUnEnclose(const aText, aEnclosedWith: string; const aCaseSens: boolean = False): string;
begin
Result := TextUnEnclose(aText, aEnclosedWith, aEnclosedWith, aCaseSens);
end;
{ Removes aLeftSide prefix from Left AND/OR aRightSide suffix from Right side of aText. }
function TextUnEnclose(const aText, aLeftSide, aRightSide: string; const aCaseSens: boolean = False): string; overload;
begin
if (aCaseSens) then
begin
if (TextLeft(aText, Length(aLeftSide)) = aLeftSide) then
Result := TextCopy(aText, Length(aLeftSide) + 1, MaxInt)
else
Result := aText;
if (TextRight(Result, Length(aRightSide)) = aRightSide) then
Delete(Result, Length(Result), Length(aRightSide));
end
else
begin
if (SameText(TextLeft(aText, Length(aLeftSide)), aLeftSide)) then
Result := TextCopy(aText, Length(aLeftSide) + 1, MaxInt)
else
Result := aText;
if (SameText(TextRight(Result, Length(aRightSide)), aRightSide)) then
Delete(Result, Length(Result), Length(aRightSide));
end;
end;
{ Find and return aIdx(th) (0-based) occurance of text in aText that is enlosed with aEnclLeft on left and }
{ aEnclRight on the right of text. If aRemEncl, aEnclLeft and aEnclRight are removed from result, aCaseSens }
{ makes the search Case-sensitive. If no enclosed text is found, result is an empty string. }
function TextFindEnclosed(const aText, aEnclLeft, aEnclRight: string; const aIdx: Integer; const aRemEncl: boolean = True; const aCaseSens: boolean = False): string;
var
a : Integer;
b : Integer;
ea: integer;
eb: integer;
l : Integer;
i : Integer;
begin
Result := CEmpty;
if (aText = CEmpty) then
Exit;
a := 1;
b := 1;
l := Length(aText);
ea := Length(aEnclLeft);
eb := Length(aEnclRight);
i := 0;
while (i <= aIdx) and (a < l) and (b < l) do
begin
a := TextPos(aText, aEnclLeft, aCaseSens, b);
if (a = 0) then
Exit;
b := TextPos(aText, aEnclRight, aCaseSens, a + ea);
if (b <= a) then
Exit;
if (i = aIdx) then
begin
if (aRemEncl) then
Result := TextCopy(aText, a + ea, b - a - ea)
else
Result := TextCopy(aText, a, b - a + eb);
end;
a := b + eb;
b := a;
Inc(i);
end; { while }
end;
{ Find and return aIdx occurance of text in aText that is enlosed with aEncl. If aRemEncl, aEncl is removed }
{ from result. aCase sens makes the search Case-sensitive. If no enclosed text is found, result is empty. }
function TextFindEnclosed(const aText, aEncl: string; const aIdx: Integer; const aRemEncl: boolean; const aCaseSens: boolean): string;
begin
Result := TextFindEnclosed(aText, aEncl, aEncl, aIdx, aRemEncl, aCaseSens);
end;
{ Encloses aText with Double quotes. "Got it?" }
function TextQuote(const aText: string): string;
begin
Result := TextEnclose(aText, CDoubleQuote);
end;
{ Removes Double quote prefix AND/OR suffix from aText. }
function TextUnquote(const aText: string): string;
begin
Result := TextUnEnclose(aText, CDoubleQuote);
end;
{ Strips $0D and $0A from end of text until it finds no more. }
function TextRemoveLineFeeds(const aText: string): string;
var
i: Integer;
begin
i := Length(aText);
while (i > 0) and ((aText[i] = CCr) or (aText[i] = CLf)) do
Dec(i);
Result := TextCopy(aText, 1, i);
end;
{ Removes string from Left of aText to aSep. If aSep is not found, nothing is returned or removed. }
{ aSep search begins from Left of aText. If aDelSep is false returns aSep as well. }
function TextExtractLeft(var aText: string; const aSep: string; const aCaseSens: boolean = False; const aDelSep: boolean = True): string;
var
i: Integer;
begin
i := TextPos(aText, aSep, aCaseSens);
if (i > 0) then
begin
Result := TextCopy(aText, 1, i - 1);
Delete(aText, 1, i - 1);
if (aDelSep) then
Delete(aText, 1, Length(aSep));
end;
end;
{ Removes string from Right of aText to aSep. If aSep is not found, nothing is returned or removed. }
{ aSep search begins from Right of aText. If aDelSep is false returns aSep as well. }
function TextExtractRight(var aText: string; const aSep: string; const aCaseSens: boolean = False; const aDelSep: boolean = True): string;
var
i, ofs: Integer;
begin
i := 0;
ofs := 1;
while (ofs <> 0) do
begin
ofs := TextPos(aText, aSep, aCaseSens, ofs);
if (ofs <> 0) then
begin
i := ofs;
Inc(ofs);
end
else
Break;
end;
if (i <> 0) then
begin
Result := TextRight(aText, Length(aText) - i);
Delete(aText, i, MaxInt);
end;
end;
{ Copies string from Left of aText to aSep. If aSep is not found, returns nothing. }
{ aSep search begins from Left of aText. }
function TextFetchLeft(const aText, aSep: string; const aCaseSens: boolean = False; const aEmptyIfNoSep: boolean = True): string;
var
i: Integer;
begin
i := TextPos(aText, aSep, aCaseSens);
if (i > 0) then
Result := TextLeft(aText, i - 1)
else if (aEmptyIfNoSep) then
Result := CEmpty
else
Result := aText;
end;
{ Copies string from Right of aText to aSep. If aSep is not found returns nothing. }
{ If aSepFromRight aSep search begins from Right of aText, else from left. }
function TextFetchRight(const aText, aSep: string; const aCaseSens: boolean; const aEmptyIfNoSep: boolean; const aSepFromRight: boolean): string;
var
i, ofs: Integer;
begin
if (aSepFromRight) then
begin
i := 0;
ofs := 1;
while (ofs <> 0) do
begin
ofs := TextPos(aText, aSep, aCaseSens, ofs);
if (ofs <> 0) then
begin
i := ofs;
Inc(ofs);
end
else
Break;
end;
if (i = 0) then
if (aEmptyIfNoSep) then
Exit(CEmpty)
else
Exit(aText);
Result := TextRight(aText, Length(aText) - i - Length(aSep) + 1);
end
else
begin
i := TextPos(aText, aSep, aCaseSens);
if (i > 0) then
Result := TextRight(aText, Length(aText) - i - Length(aSep) + 1)
else if (aEmptyIfNoSep) then
Result := CEmpty
else
Result := aText;
end;
end;
{ Copies string from Left of aText to first CRLF separator. If aSep is not found, returns nothing. }
function TextFetchLine(const aText: string): string;
begin
Result := TextFetchLeft(atext, #13#10, True);
end;
{ Removes aRemove from the Left of aText, returns the rest. }
function TextRemoveLeft(const aText, aRemove: string; const aCaseSens: boolean = False): string;
begin
if (TextBegins(aText, aRemove, aCaseSens)) then
Result := TextCopy(aText, Length(aRemove) + 1, MaxInt)
else
Result := aText;
end;
{ Removes aRemove from the Right of aText, returns the rest. }
function TextRemoveRight(const aText, aRemove: string; const aCaseSens: boolean = False): string;
begin
if (TextEnds(aText, aRemove, aCaseSens)) then
Result := TextCopy(aText, 1, Length(aText) - Length(aRemove))
else
Result := aText;
end;
{ Splits aText on aSep(s), returns an array of strings. }
function TextSplit(const aText: string; const aSep: string; const aQotStr: string; const aOptions: TSplitOptions): TArray<string>;
var
Count: Integer;
procedure Add(const aString: string);
begin
if (aString = CEmpty) then
Exit;
Inc(Count);
SetLength(Result, Count);
Result[Count - 1] := aString;
end;
var
strLen: Integer;
sepLen: Integer;
qotLen: Integer;
cpyPos: Integer;
ofsPos: Integer;
tokPos: Integer;
qotPos: Integer;
begin
if ((aText = CEmpty) or (aSep = CEmpty)) then
Exit;
if (soQuoted in aOptions) then
if (aQotStr = CEmpty) then
Exit;
Count := 0;
strLen := Length(aText);
sepLen := Length(aSep);
cpyPos := 1;
ofsPos := 1;
if (soQuoted in aOptions) then
begin
qotLen := Length(aQotStr);
qotPos := 1;
while (True) do
begin
tokPos := TextPos(aText, aSep, (soCSSep in aOptions), ofsPos);
qotPos := TextPos(aText, aQotStr, (soCSQot in aOptions), qotPos);
if (qotPos < tokPos) and (qotPos <> 0) then
begin
qotPos := TextPos(aText, aQotStr, (soCSQot in aOptions), qotPos + qotLen);
if (qotPos <> 0) then
begin
ofsPos := qotPos;
qotPos := qotPos + qotLen;
end
else
qotPos := MaxInt;
end
else
begin
if (tokPos = 0) then
begin
Add(TextCopy(aText, cpyPos, MaxInt));
Exit;
end
else
begin
if (soNoDelSep in aOptions) then
begin
if (soRemQuotes in aOptions) then
Add(TextUnEnclose(TextCopy(aText, cpyPos, tokPos - cpyPos + sepLen), aQotStr, (soCSQot in aOptions)))
else
Add(TextCopy(aText, cpyPos, tokPos - cpyPos + sepLen))
end
else
begin
if (soRemQuotes in aOptions) then
Add(TextUnEnclose(TextCopy(aText, cpyPos, tokPos - cpyPos), aQotStr, (soCSQot in aOptions)))
else
Add(TextCopy(aText, cpyPos, tokPos - cpyPos));
end;
ofsPos := tokPos + sepLen;
qotPos := ofsPos;
cpyPos := ofsPos;
end;
end;
end;
end
else
begin
while (True) do
begin
tokPos := TextPos(aText, aSep, (soCSSep in aOptions), ofsPos);
if (tokPos > 0) then
begin
if (soNoDelSep in aOptions) then
Add(TextCopy(aText, ofsPos, tokPos - ofsPos + sepLen))
else
Add(TextCopy(aText, ofsPos, tokPos - ofsPos));
ofsPos := tokPos + sepLen;
if (soSingleSep in aOptions) then
begin
Add(TextCopy(aText, ofsPos, MaxInt));
Exit;
end;
end
else
begin
Add(TextRight(aText, strLen - ofsPos + 1));
Exit;
end;
end;
end;
end;
{ Splits the line with HTML/XML markup into a list of tokens. No pair matching performed. Example: }
{ <tag1>text1</tag1><tag2>text2</tag2> to <tag1>, text1, </tag1>, <tag2>, text2 and </tag2>. }
function TextSplitMarkup(const aText: string; const aTrim: boolean): TArray<string>;
var
Count: Integer;
procedure Add(const aString: string);
begin
if (aString = CEmpty) then
Exit;
Inc(Count);
SetLength(Result, Count);
if (aTrim) then
Result[Count - 1] := Trim(aString)
else
Result[Count - 1] := aString;
end;
var
strLen: Integer;
cpyPos: Integer;
ofsPos: Integer;
begin
strLen := Length(aText);
if (strLen = 0) then
Exit;
Count := 0;
ofsPos := 1;
cpyPos := 1;
while (cpyPos <= strLen) do
begin
if (aText[cpyPos] = CLessThan) then
begin
if (ofsPos <> cpyPos) then
begin
Add(TextCopy(aText, ofsPos, cpyPos - ofsPos));
ofsPos := cpyPos;
end
else
Inc(cpyPos);
end
else if (aText[cpyPos] = CGreaterThan) then
begin
if (ofsPos <> cpyPos) then
begin
Add(TextCopy(aText, ofsPos, cpyPos - ofsPos + 1));
Inc(cpyPos);
ofsPos := cpyPos;
end
else
Inc(cpyPos);
end
else
Inc(cpyPos);
end;
if (ofsPos < cpyPos) then
Add(TextCopy(aText, ofsPos, MaxInt));
end;
{ Splits aText on aSep(s), returns TTokens record. }
function TextTokenize(const aText: string; const aSep: string; const aQotStr: string; const aOptions: TSplitOptions): TTokens;
begin
Result.FTokens := TextSplit(aText, aSep, aQotStr, aOptions);
Result.FCount := Length(Result.FTokens);
end;
{ Returns token at aIndex from aText split by aSeparator. }
function TextToken(const aText: string; const aIndex: integer; const aSeparator: string = CSpace): string;
var
tokens: TTokens;
begin
tokens := TextTokenize(aText);
Result := tokens[aIndex];
end;
{ Converts a string to an integer. }
function TextToInt(const aText: string; const aDefault: Integer): Integer;
var
code: Integer;
begin
Val(aText, Result, code);
if (code <> 0) then
Result := aDefault;
end;
{ Converts a byte to a string. }
function TextFromInt(const aByte: byte): string;
begin
{$WARNINGS OFF}
Str(aByte, Result);
{$WARNINGS ON}
end;
{ Converts an integer to a string. }
function TextFromInt(const aInteger: integer): string;
begin
{$WARNINGS OFF}
Str(aInteger, Result);
{$WARNINGS ON}
end;
{ Converts a cardinal to a string. }
function TextFromInt(const aCardinal: cardinal): string;
begin
{$WARNINGS OFF}
Str(aCardinal, Result);
{$WARNINGS ON}
end;