CBSTR StringBuilder Class

Started by Paul Squires, July 09, 2016, 11:45:45 PM

Previous topic - Next topic

Paul Squires

I worked on the StringBuilder class tonight and have attached it to this post.

The class mirrors the functionality of the StringBuilder object in PowerBasic.

Any problems, changes or additions then please let me know.
Paul Squires
PlanetSquires Software

Paul Squires

Jose, you can take this code and convert it into the standard format/style that you use for your other Afx routines. Hopefully it will be useful.
Paul Squires
PlanetSquires Software

José Roca

Should be useful both to use it independently and for functions like the ones that mimic the PB ones and that use concatenations in a loop.

José Roca

#3
Well, I have added some constructors and operators, and to allow to deference the buffer pointer without getting all of the buffer, but only the part employed by the valid string data, I have modified some of the functions to mark the end of the string data with a double null.

Now you can even use the intrinsic FB operators with it, e.g.


' We can use FB's intrinsic operators...
DIM sb AS CBStrBld = "   Test string   "
sb = TRIM(sb)
print sb


This little example demonstrates how to use it to write a wrapper function (see function AfxRemove):


'#define _CBSTR_DEBUG_ 1
#include once "CBStr.inc"
using Afx.CBStrClass

' ========================================================================================
' Returns a copy of a string with characters or strings removed.
' If cbMatchStr is not present in cbMainStr, all of cbMainStr is returned intact.
' This function is case-sensitive.
' Usage example:
' DIM cbs AS CBSTR = AfxRemove("[]Hello[]", "[]")
' MessageBoxW 0, cbs, "", MB_OK
' ========================================================================================
PRIVATE FUNCTION AfxRemove (BYREF wszMainStr AS CONST WSTRING, BYREF wszMatchStr AS WSTRING) AS CBSTR
   DIM sb AS CBStrBld = wszMainStr
   DO
      DIM nPos AS LONG = INSTR(sb, wszMatchStr)
      IF nPos = 0 THEN EXIT DO
      sb.DelChars nPos, LEN(wszMatchStr)
   LOOP
   FUNCTION = sb.Str
END FUNCTION
' ========================================================================================

DIM  i AS LONG
DIM cbs AS CBSTR
FOR i = 1 TO 100000  ' Even one million takes little time
   cbs = AfxRemove("[]Hello[]", "[]")
NEXT
PRINT cbs

' We can use FB's intrinsic operators...
DIM sb AS CBStrBld = "   Test string   "
sb = TRIM(sb)
print sb

print "Press any key..."
sleep


Even one million of calls takes very little time.

It can also be used to pass the resulting string to functions that expect a WSTRING parameter, e.g.


DIM sb AS CBstrBld = CBstrBld("&Закрыть", 1251)
SetWindowText hButton, sb
--or--
SetWindowText hButton, sb.str


Using sb.str you can also pass the resulting string to a COM method that expects a BSTR in parameter!

I have amalgamated it, together with CBSTR, in CBSTR.inc.

José Roca

Paul's test modified because I have changed the names from tAdd to Add, etc.


'#define _CBSTR_DEBUG_ 1
#include once "CBStr.inc"
using Afx.CBStrClass

dim sb as CBStrBld
dim cbs as CBSTR = "Paul"

for i as long = 1 to 100
   sb.Add cbs
next

print sb.Str

Print
print "String Length: "; sb.Len
Print "Capacity: "; sb.Capacity

Print
Print "First 5 character values before..."
For i As Long = 1 To 5
   Print sb.Char( i );
Next
Print

Print
Print "Change the first 5 character values..."
sb.Char(1) = 74
sb.Char(2) = 111
sb.Char(3) = 115
sb.Char(4) = 101
sb.Char(5) = 33

Print
Print "First 5 character values after..."
For i As Long = 1 To 5
   Print sb.Char( i );
Next
Print
Print
print sb.Str
print

Print
Print "Now delete the First 5 characters in the buffer..."
Print
print "String Length before: "; sb.Len
sb.DelChars( 1, 5 )
print "String Length after: "; sb.Len
print
print sb.Str; "************"
print

Print
Print "Clear the buffer and add some new text before doing an insert"
sb.Clear
sb.Add "12345678901234567890123456789012345678901234567890"
Print "Now insert 'PlanetSquires' (Len=13) starting at position 5..."
Print
print "String Length before: "; sb.Len
print sb.Str
cbs = "PlanetSquires"
sb.Insert( cbs, 5 )
print
print sb.Str
print "String Length after: "; sb.Len
print

print "Press any key..."
sleep


José Roca

Modified this line of the CBStrBld.ResizeBuffer function


DIM pNewBuffer AS UBYTE PTR = Allocate(nValue + 1 * SIZEOF(UBYTE) * 2)


to make room for the terminating nulls.


James Fuller

Jose,
  Going forward will all the string functions (remain, shrink,remove....) be builder functions or will we have stand alone routines?

James

Paul Squires

Thanks Jose! Excellent job. Thanks for making the class even more useful!

I have been using the CBSTR class in some new editor code and the there have only been a couple of places where the compiler complained. For example, the Dir() function needs the double asterisk to deference. I believe you have also identified Left() and Right(). Nonetheless, I use CBSTR for situations where I need a dynamic Unicode string, otherwise I simply use wstrings.
Paul Squires
PlanetSquires Software

James Fuller

Jose,
  I am confused with the relationship of the string builder class and CBStr.
CBStr's are created with SysAllocString where it appears string builder uses Allocate.

James

José Roca

#9
The DelChars function was not working properly. I apologize.

Please download this new include file.

José Roca

Quote from: James Fuller on July 11, 2016, 08:45:20 AM
Jose,
  I am confused with the relationship of the string builder class and CBStr.
CBStr's are created with SysAllocString where it appears string builder uses Allocate.

James


The relationship comes with this function, that converts the buffer to a CBSTR.


' ========================================================================================
FUNCTION CBStrBld.Str() AS CBSTR
   IF m_BufferLen = 0 THEN EXIT FUNCTION
   IF m_pBuffer = 0 THEN EXIT FUNCTION
   DIM cbOutStr AS CBSTR = SPACE(m_BufferLen \ 2)   ' class will double the size for us
   memmove(*cbOutStr, m_pBuffer, m_BufferLen)
   FUNCTION = cbOutStr
END FUNCTION
' ========================================================================================


In this wrapper


' ========================================================================================
PRIVATE FUNCTION AfxRemove (BYREF wszMainStr AS WSTRING, BYREF wszMatchStr AS WSTRING) AS CBSTR
   DIM sb AS CBStrBld = wszMainStr
   DO
      DIM nPos AS LONG = INSTR(sb, wszMatchStr)
      IF nPos = 0 THEN EXIT DO
      sb.DelChars nPos, LEN(wszMatchStr)
   LOOP
   FUNCTION = sb.Str
END FUNCTION
' ========================================================================================


All the string manipulations are done with the buffer of the CBStrBld class for speed, and in the final instruction (FUNCTION = sb.Str) we convert it to a CBSTR.


James Fuller

Jose,
  I believe this used to work?
James

#include once "windows.bi"
#include once "afx/CBStr.inc"
using Afx.CBStrClass
Function FF_Replace (cbsMain As CBStr,cbsMatch As CBStr,cbsReplace As CBStr) As CBStr
    If Len(cbsMain) = 0 OR Len(cbsMatch) = 0 OR Len(cbsReplace) = 0 Then
        Exit Function
    EndIf
    Dim As CBStr cbsOut = Type(cbsMain)
    Dim As Long i
    Do
        i = Instr(i,cbsOut,cbsMatch)
        If i  > 0 Then
            cbsOut = Left(**cbsOut,i-1) & **cbsReplace & Mid(**cbsOut,i + Len(cbsMatch))
           
            i += Len(cbsReplace)
        EndIf
    Loop Until i = 0
    Function = cbsOut
End Function
'==============================================================================
Dim As CBStr cbs = ("[]Hello[]"),cbs1="[]",cbs2="**",cbs3
cbs3 = FF_Replace(cbs,cbs1,cbs2)
? cbs3
sleep


José Roca

Quote from: TechSupport on July 11, 2016, 08:37:35 AM
Thanks Jose! Excellent job. Thanks for making the class even more useful!

I have been using the CBSTR class in some new editor code and the there have only been a couple of places where the compiler complained. For example, the Dir() function needs the double asterisk to deference. I believe you have also identified Left() and Right(). Nonetheless, I use CBSTR for situations where I need a dynamic Unicode string, otherwise I simply use wstrings.


I have added to it almost all the functionality of CBSTR. We can consider CBStrBld as a dynamic WSTRING. What it lacks is the ability to pass it to a COM method or function that has a BYREF BSTR parameter. With CBSTR we can use @cbs, that passes the adress of the underlying BSTR stored in the CBSTR class, but we can't do it with CBStrBld because the underlying type is not a BSTR, although we probably could use it with methods or functions that have a byref WSTRING parameter.

Therefore, we have now two new data types, a BSTR and a dynamic WSTRING.

José Roca

Quote from: James Fuller on July 11, 2016, 09:47:59 AM
Jose,
  I believe this used to work?
James

#include once "windows.bi"
#include once "afx/CBStr.inc"
using Afx.CBStrClass
Function FF_Replace (cbsMain As CBStr,cbsMatch As CBStr,cbsReplace As CBStr) As CBStr
    If Len(cbsMain) = 0 OR Len(cbsMatch) = 0 OR Len(cbsReplace) = 0 Then
        Exit Function
    EndIf
    Dim As CBStr cbsOut = Type(cbsMain)
    Dim As Long i
    Do
        i = Instr(i,cbsOut,cbsMatch)
        If i  > 0 Then
            cbsOut = Left(**cbsOut,i-1) & **cbsReplace & Mid(**cbsOut,i + Len(cbsMatch))
           
            i += Len(cbsReplace)
        EndIf
    Loop Until i = 0
    Function = cbsOut
End Function
'==============================================================================
Dim As CBStr cbs = ("[]Hello[]"),cbs1="[]",cbs2="**",cbs3
cbs3 = FF_Replace(cbs,cbs1,cbs2)
? cbs3
sleep



No. You must start INSTR with at least 1. Change


Dim As Long i


to


Dim As Long i = 1


José Roca

This is a faster version:


' ========================================================================================
' Within a specified string, replace all occurrences of one string with another string.
' Replaces all occurrences of cbMatchStr in cbMainStr with cbReplaceWith
' The replacement can cause cbMainStr to grow or condense in size.
' When a match is found, the scan for the next match begins at the position immediately
' following the prior match.
' This function is case-sensitive.
' Usage example:
' DIM cbs AS CBSTR = AfxReplace("Hello World", "World", "Earth")
' MessageBoxW 0, cbs, "", MB_OK
' ========================================================================================
PRIVATE FUNCTION AfxReplace (BYREF wszMainStr AS WSTRING, BYREF wszMatchStr AS WSTRING, BYREF wszReplaceWith AS CBSTR) AS CBSTR
   DIM sb AS CBStrBld = wszMainStr
   DIM nPos AS LONG = 1
   DO
      nPos = INSTR(nPos, sb, wszMatchStr)
      IF nPos = 0 THEN EXIT DO
      sb = MID(sb, 1, nPos - 1) & wszReplaceWith & MID(sb, nPos + LEN(wszMatchStr))
      nPos += LEN(wszReplaceWith)
   LOOP
   FUNCTION = sb.Str
END FUNCTION
' ========================================================================================