Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
String Find and Replace
#1
Code: (Select All)
$Console:Only
t$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZAABVA"
Print Using "There are ## A's in "; String.Find(t$, "A", 0);
Print t$
Print "The first  B is at position "; String.Find(t$, "B", 1)
Print "The second B is at position "; String.Find(t$, "B", 2)
Print "The third  B is at position "; String.Find(t$, "B", 3)
Print
Print "Replace the A's with ' Hello World ', we get:"
t1$ = String.Replace(t$, "A", " Hello World ")
Print t1$


Function String.Find& (Content As String, Search As String, CountTo As Long)
    'If CountTo is 0, this routine will count the number of instances of the search term inside the content string
    'If CountTo is >0, this routine will find the position of that instance, if it exists, inside the search string.
    'For example, CountTo = 2, it'll find the location of the 2nd instance of the search term, skipping the first.
    If CountTo < 0 Then Exit Function 'Can't find a negative position.
    Dim As Long p, l
    If Search$ = "" Then Exit Function
    p = InStr(Content$, Search$)
    l = Len(Search$)
    Do While p& > 0
        Count = Count + 1
        If CountTo = Count Then String.Find = p: Exit Function
        p = InStr(p + l, Content$, Search$)
    Loop
    If CountTo = 0 Then String.Find = Count
End Function

Function String.Replace$ (content$, from$, to$)
    'Inspired by the forum post here: https://qb64phoenix.com/forum/showthread...9#pid33559
    'Original credit goes to mdijkens as I just tweaked it a little to make it a bit more library friendly
    $Checking:Off
    Dim As Long mp, pp, found
    Dim m As _MEM
    found = String.Find(content$, from$, 0)
    flen& = Len(from$): tlen& = Len(to$)

    m = _MemNew(Len(content$) + (tlen& - flen&) * found): mp = 0: pp = 1
    p& = InStr(content$, from$)
    Do While p& > 0
        _MemPut m, m.OFFSET + mp, Mid$(content$, pp, p& - pp): mp = mp + p& - pp
        _MemPut m, m.OFFSET + mp, to$: mp = mp + tlen&: pp = p& + flen&
        p& = InStr(p& + flen&, content$, from$)
    Loop
    _MemPut m, m.OFFSET + mp, Mid$(content$, pp): mp = mp + Len(Mid$(content$, pp))
    content2$ = Space$(mp): _MemGet m, m.OFFSET, content2$: _MemFree m
    String.Replace$ = content2$
    $Checking:On
End Function

Two routines in here which may be useful for folks:

String.Find will either tell you how many times something appears inside a string (Good for quick counting of said item.), or else it'll give the position of the Xth item inside that string.

String.Replace is a quick replacement routine which is based off the posts here: https://qb64phoenix.com/forum/showthread...9#pid33559  The main difference is that it properly sizes the return buffer so as to work easier in any library/plug in file.  It's a bit slower than the original, but safer and less memory intensive in most cases.
Reply
#2
Tweaked the routines slightly to be Option _Explicit compatible:

Code: (Select All)
Function String.Find& (Content As String, Search As String, CountTo As Long)
'If CountTo is 0, this routine will count the number of instances of the search term inside the content string
'If CountTo is >0, this routine will find the position of that instance, if it exists, inside the search string.
'For example, CountTo = 2, it'll find the location of the 2nd instance of the search term, skipping the first.
If CountTo < 0 _OrElse Search$ = "" Then Exit Function 'Can't find a negative position.
Dim As Long p, l, count
p = InStr(Content$, Search$)
l = Len(Search$)
Do While p& > 0
count = count + 1
If CountTo = count Then String.Find = p: Exit Function
p = InStr(p + l, Content$, Search$)
Loop
If CountTo = 0 Then String.Find = count
End Function

Function String.Replace$ (content$, from$, to$)
'Inspired by the forum post here: https://qb64phoenix.com/forum/showthread...9#pid33559
'Original credit goes to mdijkens as I just tweaked it a little to make it a bit more library friendly
$Checking:Off
If from$ = "" _OrElse content$ = "" Then Exit Function 'can't replace nothing
Dim As Long mp, pp, found, flen, tlen, p
Dim m As _MEM, content2 As String
found = String.Find(content$, from$, 0)
flen = Len(from$): tlen = Len(to$)

m = _MemNew(Len(content$) + (tlen& - flen&) * found): mp = 0: pp = 1
p = InStr(content$, from$)
Do While p > 0
_MemPut m, m.OFFSET + mp, Mid$(content$, pp, p - pp): mp = mp + p - pp
_MemPut m, m.OFFSET + mp, to$: mp = mp + tlen: pp = p + flen
p = InStr(p + flen, content$, from$)
Loop
_MemPut m, m.OFFSET + mp, Mid$(content$, pp): mp = mp + Len(Mid$(content$, pp))
content2 = Space$(mp): _MemGet m, m.OFFSET, content2: _MemFree m
String.Replace$ = content2
$Checking:On
End Function
Reply
#3
@bplus See what you think of this style string replacement:

Code: (Select All)
O$ = "12/24/2025"
Print String.Replace.Part(O$, "...??.....", "??", "31") 'string replace part allows us to set a pattern and change our original
Print String.Replace.Part(O$, "..?..?....", "?", "-") 'in relation to that pattern, which is useful for stuff like:
Print
Print
Print Date$ 'the originial version
Print String.Convert.Date(Date$, "Mm-Dd-Year", "Year-Mm-Dd") 'change the pattern from MM-DD-YYYY to YYYY-MM-DD
Print String.Convert.Date(Date$, "Mm-Dd-Year", "Dd-Mm-Year") 'to the weird other side of the world pattern of DD-MM-YYYY

Function String.Convert.Date$ (Source As String, oFormat As String, nFormat As String)
Dim T As String
T = Source
For i = 1 To Len(T)
T = String.Replace.Part(T, nFormat, Mid$(oFormat, i, 1), Mid$(Source, i, 1))
Next
String.Convert.Date = T
End Function

Function String.Get.Part$ (Source As String, Format As String, Part As String)
'this routine allows us to get partial information from a properly formatted string
'format should be something similar to:
'YYYY/MM/DD
'YY-MM-DD
'or similar. What we're looking for is the PART we specify in that format.
Dim As Long p

p = InStr(UCase$(Format), Part)
If Len(Source) <> Len(Format) _OrElse p = 0 Then
_MessageBox "Bad Format", "Error: Passing String with invalid format to Function String.Get.Part.", "error"
Else
String.Get.Part = Mid$(Source, p, Len(Part))
End If
End Function


Function String.Replace.Part$ (Source As String, Format As String, Part As String, Replacement As String)
'this routine allows us to get partial information from a properly formatted string
'format should be something similar to:
'YYYY/MM/DD
'YY-MM-DD
'or similar. What we're looking for is the PART we specify in that format.
Dim As Long p
Dim As String T
T = Source
p = InStr(Format, Part)
If Len(Source) <> Len(Format) _OrElse p = 0 _OrElse Len(Part) <> Len(Replacement) Then
_MessageBox "Bad Format", "Error: Passing String with invalid format to Function String.Replace.Part.", "error"
Else
Do
Mid$(T, p, Len(Replacement)) = Replacement
String.Replace.Part = T
p = InStr(p + 1, Format, Part)
Loop Until p = 0
End If
End Function

Instead of just saying "Replace CAT with DOG", this lets you specify patterns to replace so you can completely reformat strings and such very quick and easily.

Note that since this requires set patterns, you can't replace "APPLE" with "BANANAORANGE" as the patterns don't match. It shouldn't be too hard to swap it to do such, but that's not something I was looking for here. I'm just looking to set a pattern, a replacement pattern, and then replace those chose parts quickly and easily. Wink
Reply
#4
Man Steve you are really on a roll! Smile

Tonight I am finishing other business but I will be happy to have something to do when insomnia strikes again.
b = b + ...
Reply
#5
Not well designed:
The QB Code Window sucks when text goes past the width of code window such that you have to scroll down to use the horizontal scroll to go sides ways, so once you're over to the right far enough you've lost your clues to what line you were reading from. Hence you are forced to copy and paste into IDE or other text editor to read the code properly or just give up and move on.

So @SMcNeill I have looked over code and see all your examples apply to dates. Is that the limit or target or subject of all the Format variables? (format can cover allot of ground not just dates.) Or can this be applied more generally like StrReplace$ routines? (since you asked me what I think) Also String.Get.Part is not demo'd in your code above, artifact of some other string code app?
b = b + ...
Reply
#6
Aye, it's for anything that you might apply a set format for.

The String.Get.Part let's us specify a part of a string that we're searching for, which might change formatting depending on area or local.   

For example area lets go with "LLLLLLLLLLLLLLL, FFFFFFFFFFFFFFF".

Now on one format, that might be a string for Last name, First name (as designated by L and F.)
Instead of manually having to hard code into our program "Last name starts at position 1, is 15 characters.  First name is at position 17, is 15 characters.", or parse for commas and such, we just set a form pattern in that format and specify we want the part called "FFFFFFFFFFFFFFF" for the first name.

One form might have First name then last.  Another might have Last name then first.  A third form might have middle initial.   A fourth might have room for a title or suffix.  "DR Joe D Blowhard III, ESQ".    As long as we know the pattern, we can just grab out the part that we want from that form.

Dates were where this concept started, simply because it's something that we see which varies a lot.

MM/DD/YYYY  <-- US dates.
DD/MM/YYYY  <-- European dates.
YYYY/MM/DD  <-- Easily sorted computer dates.

Of course there's also various separators per region.  "MM/DD/YYYY" vs "MM-DD-YYYY" vs "MM.DD.YYYY" vs...  agggh!!!  The data we might want to gather might be shuffled around inside the damn localized formatting and be anywhere in the flipping world inside that string!!

So, this asks us to specify the localized format string that we should use and then asks for the part that we're looking for.

My format$ is "MM/DD/YYYY", I want the "MM" from this one.
Click.  format$ is now "DD/MM/YYYY".  I still want the "MM" from this one.
Click.  format$ is now "WWW, MM DD, YYYY".  I want the "MM".  (format such as "Fri, 03 14, 2025")

Give it the source$, the format$, and the part of that format$ and it fetches that part back for you.  That's what String.Get.Part does.  Smile

The String.Part.Replace works the same way.  You may not know where the MM is if the user changes local, but you want to update the info for one part in that form.   You simply send it the source string and valid format, tell it the part you're looking to replace, and then replace it with the proper data -- wherever it occurs in the string.

It's the flexibility to allow *the users* to set a custom format for a form so that it fits their localized standards, and for you to still be able to get and replace the information out of it.

Any place where data might be jumbled to a different format, all you need to know is the format style, specify it, and then you can grab or replace that part of the data.

"Steve.... 51 (555) 555-5555 123 Steve House Steveland, Stevestate 12345"

There's a lot of information in the line up above.  Name, age, phone number, street address city, state zipcode.  Any of that could appear in various forms, or not.  Some forms might have extra information like Last Name included.  Others might have some of that info removed for privacy reasons.    

None of that matter though as long as I can specify a format string for the data I'm looking for.  

I want ".........??......................................................................................." the data that corresponds to my ?? in this form.  That's my AGE.   In another form it might be in a different position.  Doesn't matter.  I just say I want "..........??" that part of that form.

Take any string.  Specify the format that it's in.  Choose the part you want.  Get or Replace that part if you like. 

^ That's what the functions above do for us.  Dates are just a good example and easy thing to use for them, but they allow for the flexibility to grab the data we want, no matter the format that it's in, and interact with it for us.  Wink
Reply




Users browsing this thread: 1 Guest(s)