source: branches/2.19_branch/Library/ACLString.pas@ 338

Last change on this file since 338 was 17, checked in by RBRi, 19 years ago

+ Library

  • Property svn:eol-style set to native
File size: 19.1 KB
Line 
1Unit ACLString;
2
3Interface
4
5uses
6{$Ifdef os2}
7 BseDos,
8{$endif}
9 SysUtils;
10
11{
12 AString: A length-safe string class. Internally maintains
13 a length as well as a zero terminator, so very fast for concatenation
14
15 Attributes:
16 - zero-based indexing
17 - maintains zero terminator for passing as (readonly) c-style string (AsPChar)
18 - fast for concatenation
19 - no inherent length limit
20
21 You can write to it as pchar by calling SetMaxLength, using AsPChar,
22 then calling UpdateLength to set the length based on the term.
23
24 For Sibyl it is clumsier but faster for concatenating
25 than AnsiString and I trust it more...
26
27 For extra safety it explicitly checks that this is a valid instance of
28 AString on every method call (using an internal magic number)
29 Note :- because of the exception handling this is probably quite slow.
30
31 You can also call the global procedure
32 CheckAllAStringsDestroyed
33 at the end of the program to make sure there are no memory leaks
34 due to AStrings not being destroyed.
35
36 V1.2 28/6/00
37 Added ReadLn and WriteLn methods (for text files)
38 Added Character index property
39 V1.1 27/6/00
40 Added:
41 Delete - delete a seciton of string
42 Assign methods
43 AsString property
44 CharPosition function
45 ExtractNextValue method
46 This method, unlike my original string version, does not
47 alter the main string. Rather it takes and increments
48 a starting position.
49
50 V1.0
51 Completed basic functionality
52 Used in NewView for decoding help topics. Fast!
53}
54
55type
56 EAStringError = class( Exception );
57 EAStringIndexError = class( EAStringError );
58
59 TAString = class
60 private
61 function GetIsEmpty: boolean;
62 protected
63 _S: PChar;
64 _Length: longint;
65 _MagicNumber: longword;
66 procedure CheckSize( const NeededLength: longint );
67 procedure AddData( const Data: pointer;
68 const DataLength: longint );
69
70 Procedure InsertData( const Data: pointer;
71 const InsertPoint: longint;
72 const DataLength: longint );
73
74 procedure Initialise;
75
76 function ValidIndex( const Index: longint ): boolean;
77 procedure CheckIndex( const Index: longint );
78 function GetAsString: string;
79
80 procedure SetLength( NewLength: longint );
81
82 function GetChar( Index: longint ): Char;
83 procedure SetChar( Index: longint;
84 const Value: Char );
85
86 public
87
88 constructor Create;
89 constructor CreateFrom( const S: String );
90 constructor CreateFromPChar( const S: PChar );
91 constructor CreateFromPCharLen( const S: PChar; const Len: longint );
92 constructor CreateCopy( const S: TAString );
93
94 // Create a AString from the given PChar and
95 // dispose of the PChar. Useful for using when you can only
96 // get a PChar as a newly allocated string (e.g TMemo.Lines.GetText)
97 constructor CreateFromPCharWithDispose( const S: PChar );
98
99 destructor Destroy; override;
100
101 // Modifications
102 procedure Assign( const S: TAString );
103 procedure AssignString( const S: string );
104 procedure AssignPChar( const S: PChar );
105 procedure AssignPCharLen( const S: PChar; const Len: longint );
106
107 procedure Add( const S: TAString );
108 procedure AddString( const S: string );
109 procedure AddPChar( const S: PChar );
110 procedure AddPCharLen( const S: PChar; const Len: longint );
111 procedure AddChar( const C: char );
112
113 Procedure Insert( const InsertPoint: longword;
114 const S: TAString );
115 Procedure InsertString( const InsertPoint: longword;
116 const S: string );
117
118 procedure Trim;
119 procedure TrimChar( CharToTrim: Char );
120 procedure Delete( const StartingFrom: longint;
121 const LengthToDelete: longint );
122 procedure Clear;
123
124 // Properties
125 property AsPChar: PChar read _S;
126 property AsString: string read GetAsString;
127 property Character[ Index: longint ]: Char read GetChar write SetChar; default;
128 property Length: longint read _Length write SetLength;
129 property IsEmpty: boolean read GetIsEmpty;
130
131 // updates length based on terminator, use after writing to as pchar
132 Procedure UpdateLength;
133
134 // Queries
135 // returns -1 if not found
136 function CharPosition( const StartingFrom: longint;
137 const CharToFind: Char ): longint;
138
139 function SameAs( const S: String ): boolean;
140
141 // returns true if starts with S
142 function StartsWith( const S: String ): boolean;
143
144 // Extract the next value seperated by seperator
145 // starting at StartingFrom (zero based index!)
146 procedure ExtractNextValue( Var StartingFrom: longint;
147 ExtractTo: TAString;
148 const Seperator: Char );
149 procedure GetRightFrom( const StartingFrom: longint;
150 Dest: TAString );
151 procedure GetLeft( const Count: longint;
152 Dest: TAString );
153 procedure GetRight( const Count: longint;
154 Dest: TAString );
155 procedure ParseKeyValuePair( KeyName: TAString;
156 KeyValue: TAString;
157 Seperator: Char );
158
159 // Make sure the string can contain at least MaxLength chars
160 // Use before passing AsPChar to a function that writes a PChar
161 procedure SetMaxLength( MaxLength: longint );
162
163 // Read a line from the given file. Line must end
164 // with #13 #10. ( Single #13 or #10 not recognised )
165 procedure ReadLn( Var TheFile: TextFile );
166 procedure WriteLn( Var TheFile: TextFile );
167
168{$Ifdef os2}
169 // Read a line from the given file handle
170 function ReadParagraph( F: HFile ): boolean;
171{$endif}
172 end;
173
174// call this to be sure all AStrings have been destroyed.
175procedure CheckAllAStringsDestroyed;
176
177Implementation
178
179uses
180{$Ifdef os2}
181 OS2Def, ACLFileIOUtility,
182{$endif}
183 ACLUtility, ACLStringUtility;
184
185const
186 GlobalAStringCreatedCount: longint = 0;
187 GlobalAStringDestroyedCount: longint = 0;
188
189const
190 MagicConstant = $cabba9e;
191
192procedure CheckAllAStringsDestroyed;
193begin
194 if GlobalAStringCreatedCount > GlobalAStringDestroyedCount then
195 raise Exception.Create( 'Not all AStrings have been destroyed ('
196 + IntToStr( GlobalAStringCreatedCount )
197 + ' created, '
198 + IntToStr( GlobalAStringDestroyedCount )
199 + ' destroyed). Possible memory leak.' );
200end;
201
202procedure CheckValid( const S: TAString );
203var
204 IsValid: boolean;
205begin
206 try
207 IsValid:= S._MagicNumber = MagicConstant;
208 except
209 IsValid:= false;
210 end;
211 if not IsValid then
212 raise Exception.Create( 'Reference to invalid AString' );
213end;
214
215constructor TAString.Create;
216begin
217 inherited Create;
218 Initialise;
219end;
220
221procedure TAString.Initialise;
222begin
223 inc( GlobalAStringCreatedCount );
224 _S:= StrAlloc( 16 );
225 _MagicNumber:= MagicConstant;
226 Clear;
227end;
228
229constructor TAString.CreateFrom( const S: String );
230begin
231 Initialise;
232 AssignString( S );
233end;
234
235constructor TAString.CreateFromPChar( const S: PChar );
236begin
237 Initialise;
238 AssignPChar( S );
239end;
240
241constructor TAString.CreateFromPCharLen( const S: PChar; const Len: longint );
242begin
243 Initialise;
244 AssignPCharLen( S, Len );
245end;
246
247constructor TAString.CreateFromPCharWithDispose( const S: PChar );
248begin
249 Initialise;
250 AddPChar( S );
251 StrDispose( S );
252end;
253
254constructor TAString.CreateCopy( const S: TAString );
255begin
256 Initialise;
257 Assign( S );
258end;
259
260destructor TAString.Destroy;
261begin
262 inc( GlobalAStringDestroyedCount );
263 StrDispose( _S );
264 _MagicNumber:= 0;
265 inherited Destroy;
266end;
267
268procedure TAString.CheckSize( const NeededLength: longint );
269var
270 temp: PChar;
271 NewBufferSize: longint;
272 CurrentBufferSize: longint;
273begin
274 CurrentBufferSize:= StrBufSize( _S );
275 if NeededLength + 1 > CurrentBufferSize then
276 begin
277 // allocate new buffer, double the size...
278 NewBufferSize:= CurrentBufferSize * 2;
279 // or if that's not enough...
280 if NewBufferSize < NeededLength + 1 then
281 // double what we are going to need
282 NewBufferSize:= NeededLength * 2;
283
284 temp:= StrAlloc( NewBufferSize );
285
286 MemCopy( _S,
287 Temp,
288 _Length + 1 );
289
290 StrDispose( _S );
291 _S:= temp;
292 end;
293end;
294
295procedure TAString.Clear;
296begin
297 CheckValid( self );
298 _Length:= 0;
299 _S[ 0 ]:= #0;
300end;
301
302procedure TAString.AddData( const Data: pointer;
303 const DataLength: longint );
304begin
305 if DataLength = 0 then
306 exit;
307 CheckSize( _Length + DataLength );
308 MemCopy( Data, _S + _Length, DataLength );
309 inc( _Length, DataLength );
310 _S[ _Length ]:= #0;
311end;
312
313Procedure TAString.InsertData( const Data: pointer;
314 const InsertPoint: longint;
315 const DataLength: longint );
316begin
317 // we can also insert at the very end... which is not a valid index
318 if InsertPoint <> _Length then
319 if not ValidIndex( InsertPoint ) then
320 exit;
321
322 CheckSize( _Length + DataLength );
323
324 // shift the existing text, past the insert point,
325 // up to make room
326 MemCopy( _S + InsertPoint,
327 _S + InsertPoint + DataLength,
328 _Length - InsertPoint );
329
330 MemCopy( Data,
331 _S + InsertPoint,
332 DataLength );
333
334 inc( _Length, DataLength );
335
336 // zero terminate
337 _S[ _Length ] := #0;
338
339end;
340
341procedure TAString.Add( const S: TAString );
342begin
343 CheckValid( self );
344 CheckValid( S );
345 AddData( S._S, S.Length );
346end;
347
348procedure TAString.AddPChar( const S: PChar );
349begin
350 CheckValid( self );
351 AddData( S, StrLen( S ) );
352end;
353
354procedure TAString.AddString( const S: string );
355begin
356 CheckValid( self );
357{$ifdef os2}
358 AddData( Addr( S ) + 1, System.Length( S ) );
359{$else}
360 AddData( PChar( S ), System.Length( S ) );
361{$endif}
362end;
363
364procedure TAString.AddChar( const C: char );
365begin
366 CheckValid( self );
367 AddData( Addr( C ), 1 );
368end;
369
370procedure TAString.TrimChar( CharToTrim: Char );
371var
372 StartP: PChar;
373 EndP: PChar;
374 C: Char;
375begin
376 CheckValid( self );
377 if _Length = 0 then
378 exit;
379 StartP:= _S;
380 EndP:= _S + Length;
381
382 while StartP < EndP do
383 begin
384 C:= StartP^;
385 if C <> CharToTrim then
386 break;
387 inc( StartP );
388 end;
389 // StartP now points to first non-space char
390
391 while EndP > StartP do
392 begin
393 dec( EndP );
394 C:= EndP^;
395 if C <> CharToTrim then
396 begin
397 inc( EndP );
398 break;
399 end;
400 end;
401 // EndP now points to one byte past last non-space char
402
403 _Length:= PCharDiff( EndP, StartP );
404
405 if _Length > 0 then
406 if StartP > _S then
407 MemCopy( StartP, _S, _Length );
408
409 _S[ _Length ]:= #0;
410
411end;
412
413procedure TAString.ExtractNextValue( Var StartingFrom: longint;
414 ExtractTo: TAString;
415 const Seperator: Char );
416var
417 NextSeperatorPosition: longint;
418begin
419 CheckValid( self );
420 CheckValid( ExtractTo );
421
422 ExtractTo.Clear;
423 if StartingFrom >= Length then
424 exit;
425 NextSeperatorPosition:= CharPosition( StartingFrom,
426 Seperator );
427 if NextSeperatorPosition > -1 then
428 begin
429 ExtractTo.AddData( _S + StartingFrom,
430 NextSeperatorPosition - StartingFrom );
431 StartingFrom:= NextSeperatorPosition + 1;
432 end
433 else
434 begin
435 ExtractTo.AddData( _S + StartingFrom,
436 Length - StartingFrom );
437 StartingFrom:= Length;
438 end;
439 ExtractTo.Trim;
440
441end;
442
443procedure TAString.Assign( const S: TAString );
444begin
445 Clear;
446 Add( S );
447end;
448
449procedure TAString.AssignPChar( const S: PChar);
450begin
451 Clear;
452 AddPChar( S );
453end;
454
455procedure TAString.AssignPCharLen( const S: PChar; const Len: longint );
456begin
457 Clear;
458 AddPCharLen( S, Len );
459end;
460
461procedure TAString.AssignString( const S: string );
462begin
463 Clear;
464 AddString( S );
465end;
466
467function TAString.CharPosition( const StartingFrom: longint;
468 const CharToFind: Char ): longint;
469var
470 StartP: PChar;
471 P: PChar;
472 EndP: PChar;
473 C: Char;
474begin
475 CheckValid( self );
476 Result:= -1;
477 if not ValidIndex( StartingFrom ) then
478 exit;
479 StartP:= _S + StartingFrom;
480 EndP:= _S + Length;
481 P:= StartP;
482
483 while P < EndP do
484 begin
485 C:= P^;
486 if C = CharToFind then
487 begin
488 Result:= PCharDiff( p, _S );
489 break;
490 end;
491 inc( P );
492 end;
493end;
494
495procedure TAString.Delete( const StartingFrom: longint;
496 const LengthToDelete: longint );
497var
498 StartP: PChar;
499 EndP: PChar;
500 SizeToCopy: longint;
501begin
502 if not ValidIndex( StartingFrom ) then
503 exit;
504 if LengthToDelete = 0 then
505 exit;
506
507 StartP:= _S + StartingFrom;
508 if StartingFrom + LengthToDelete >= Length then
509 begin
510 SetLength( StartingFrom );
511 exit;
512 end;
513 EndP:= _S + StartingFrom + LengthToDelete;
514 SizeToCopy:= Length - ( StartingFrom + LengthToDelete );
515 MemCopy( EndP, StartP, SizeToCopy );
516 SetLength( Length - LengthToDelete );
517end;
518
519function TAString.ValidIndex( const Index: longint ): boolean;
520begin
521 Result:= ( Index >= 0 ) and ( Index < Length );
522end;
523
524function TAString.GetAsString: string;
525begin
526 CheckValid( self );
527{$ifdef os2}
528 Result:= StrPas( _S );
529{$else}
530 Result:= _S;
531{$endif}
532end;
533
534procedure TAString.SetLength( NewLength: longint );
535begin
536 CheckValid( self );
537 if NewLength < 0 then
538 exit;
539 CheckSize( NewLength );
540 _Length:= NewLength;
541 _S[ _Length ]:= #0;
542
543end;
544
545procedure TAString.ReadLn( var TheFile: TextFile );
546Var
547 C: Char;
548 FoundCR: boolean;
549Begin
550 CheckValid( self );
551 Clear;
552 FoundCR:= false;
553 while not eof( TheFile ) do
554 begin
555 Read( TheFile, C );
556 if ( C = #10 ) then
557 begin
558 if FoundCR then
559 exit; // reached end of line
560 end
561 else
562 begin
563 if FoundCR then
564 // last CR was not part of CR/LF so add to string
565 AddString( #13 );
566 end;
567 FoundCR:= ( C = #13 );
568 if not FoundCR then // don't handle 13's till later
569 begin
570 AddString( C );
571 end;
572 end;
573
574 if FoundCR then
575 // CR was last char of file, but no LF so add to string
576 AddString( #13 );
577
578end;
579
580procedure TAString.WriteLn( var TheFile: TextFile );
581var
582 P: PChar;
583 EndP: PChar;
584 C: Char;
585begin
586 CheckValid( self );
587
588 P:= _S;
589 EndP:= _S + Length;
590
591 while P < EndP do
592 begin
593 C:= P^;
594 Write( TheFile, C );
595 inc( P );
596 end;
597 Write( TheFile, #13 );
598 Write( TheFile, #10 );
599end;
600
601function TAString.GetChar( Index: longint ): Char;
602begin
603 CheckValid( self );
604 CheckIndex( Index );
605 Result:= _S[ Index ];
606end;
607
608procedure TAString.SetChar( Index: longint;
609 const Value: Char );
610begin
611 CheckValid( self );
612 CheckIndex( Index );
613 _S[ Index ]:= Value;
614end;
615
616procedure TAString.CheckIndex( const Index: longint );
617begin
618 if not ValidIndex( Index ) then
619 raise EAStringIndexError( 'Index '
620 + IntToStr( Index )
621 + ' is not in valid range ( 0 - '
622 + IntToStr( Length - 1 )
623 + ') for string' );
624
625end;
626
627procedure TAString.ParseKeyValuePair( KeyName: TAString;
628 KeyValue: TAString;
629 Seperator: Char );
630var
631 Position: longint;
632begin
633 CheckValid( self );
634 Position:= 0;
635 ExtractNextValue( Position, KeyName, Seperator );
636 GetRightFrom( Position, KeyValue );
637end;
638
639
640procedure TAString.GetLeft( const Count: longint;
641 Dest: TAString );
642begin
643 CheckValid( self );
644 Dest.Clear;
645 if Count >= Length then
646 Dest.Assign( self )
647 else if Count > 0 then
648 Dest.AddData( _S, Count );
649end;
650
651procedure TAString.GetRight( const Count: longint;
652 Dest: TAString );
653begin
654 CheckValid( self );
655 Dest.Clear;
656 if Count >= Length then
657 Dest.Assign( self )
658 else if Count > 0 then
659 Dest.AddData( _S + Length - Count - 1, Count );
660end;
661
662procedure TAString.GetRightFrom( const StartingFrom: longint;
663 Dest: TAString );
664begin
665 CheckValid( self );
666 Dest.Clear;
667 if StartingFrom <= 0 then
668 Dest.Assign( self )
669 else if StartingFrom < Length then
670 Dest.AddData( _S + StartingFrom, Length - StartingFrom );
671end;
672
673{$ifdef os2}
674function TAString.SameAs( const S: String ): boolean;
675var
676 DataCompareResult: integer;
677begin
678 CheckValid( self );
679 if Length <> System.Length( S ) then
680 begin
681 // different lengths -> strings are different
682 result := false;
683 exit;
684 end;
685 // length is same, so compare data, case insensitive
686 DataCompareResult := StrLIComp( _S,
687 Addr( S ) + 1,
688 Length );
689 Result := DataCompareResult = 0;
690end;
691
692// returns true if starts with S
693function TAString.StartsWith( const S: String ): boolean;
694var
695 DataCompareResult: integer;
696begin
697 CheckValid( self );
698 if System.Length( S ) > Length then
699 begin
700 // S is longer than us; we can't start with it
701 result := false;
702 exit;
703 end;
704 // compare data, case insensitive
705 DataCompareResult := StrLIComp( _S,
706 Addr( S ) + 1,
707 System.Length( S ) );
708 Result := DataCompareResult = 0;
709end;
710
711{$else} // win32
712
713// returns true if starts with S
714function TAString.StartsWith( const S: String ): boolean;
715var
716 DataCompareResult: integer;
717begin
718 CheckValid( self );
719 if System.Length( S ) > Length then
720 begin
721 // S is longer than us; we can't start with it
722 result := false;
723 exit;
724 end;
725 // compare data, case insensitive
726 DataCompareResult := StrLIComp( _S,
727 PChar(s),
728 System.Length( S ) );
729 Result := DataCompareResult = 0;
730end;
731
732function TAString.SameAs( const S: String ): boolean;
733begin
734 CheckValid( self );
735 Result:= StrIComp( _S, PChar( S ) ) = 0;
736end;
737{$endif}
738
739function TAString.GetIsEmpty: boolean;
740begin
741 CheckValid( self );
742 Result:= Length = 0;
743end;
744
745procedure TAString.Trim;
746begin
747 CheckValid( self );
748 TrimChar( #32 );
749end;
750
751procedure TAString.SetMaxLength( MaxLength: longint );
752begin
753 CheckValid( self );
754 CheckSize( MaxLength );
755end;
756
757procedure TAString.AddPCharLen( const S: PChar; const Len: longint );
758begin
759 CheckValid( self );
760 AddData( S, Len );
761end;
762
763Procedure TAString.Insert( const InsertPoint: longword;
764 const S: TAString );
765begin
766 CheckValid( self );
767 InsertData( S._S,
768 InsertPoint,
769 S._Length );
770end;
771
772Procedure TAString.InsertString( const InsertPoint: longword;
773 const S: string );
774begin
775 CheckValid( self );
776{$ifdef os2}
777 InsertData( Addr( S ) + 1,
778 InsertPoint,
779 System.Length( S ) );
780{$else}
781 InsertData( PChar( S ),
782 InsertPoint,
783 System.Length( S ) );
784{$endif}
785end;
786
787{$Ifdef os2}
788function TAString.ReadParagraph( F: HFile ): boolean;
789var
790 C: Char;
791 NewFilePtr: ULONG;
792begin
793 Clear;
794
795 Result:= MyRead( F, Addr( C ), 1 );
796 while ( C <> #13 )
797 and Result do
798 begin
799 AddChar( C );
800 Result:= MyRead( F, Addr( C ), 1 );
801 end;
802
803 if not Result then
804 exit;
805
806 // skip #10 if found
807 Result:= MyRead( F, Addr( C ), 1 );
808 if Result then
809 if C <> #10 then
810 DosSetFilePtr( F, -1, FILE_CURRENT, NewFilePtr );
811end;
812{$endif}
813
814// updates length based on terminator, use after writing to as pchar
815Procedure TAString.UpdateLength;
816begin
817 SetLength( StrLen( _S ) );
818end;
819
820Initialization
821End.
Note: See TracBrowser for help on using the repository browser.