-
Mar 17th, 2025, 06:18 AM
#1
Thread Starter
Junior Member
String Interpolation, String Formatting, Composite Formatting, Template Literals
Hi! Seniors,
Please suggest good libs/code for VB6/VBA and should be competitive with following languages.
Code:
C# (.NET) |
Composite Formatting |
String.Format("{0}, {1}", "Hello", "World") |
|
String Interpolation ($"...") |
$"Hello, {name}!" |
Python |
String Formatting (str.format) |
"Hello, {}!".format("World") |
|
f-strings (String Interpolation, Python 3.6+) |
f"Hello, {name}!" |
Java |
Formatting with String.format |
String.format("%s, %s", "Hello", "World") |
JavaScript |
Template Literals |
`Hello, ${name}!` |
|
String Concatenation (Basic) |
"Hello, " + name + "!" |
Ruby |
Interpolation with #{} |
"Hello, #{name}!" |
|
sprintf or format |
sprintf("Hello, %s!", name) |
PHP |
Double-Quoted Strings with Variables |
"Hello, $name!" |
|
sprintf |
sprintf("Hello, %s!", $name) |
C++ |
printf/sprintf |
sprintf(buffer, "Hello, %s!", name); |
|
String Interpolation (C++20, std::format) |
std::format("Hello, {}!", name) |
Go |
fmt.Sprintf |
fmt.Sprintf("Hello, %s!", name) |
Rust |
Formatting Macros |
format!("Hello, {}!", name) |
Kotlin |
String Templates |
"Hello, $name!" |
|
String.format (Java-style) |
"Hello, %s!".format(name) |
Swift |
String Interpolation |
print("Hello, \(name)!") |
Perl |
String Interpolation (Double-Quoted Strings) |
"Hello, $name!" |
|
sprintf |
sprintf("Hello, %s!", $name) |
Dart |
String Interpolation |
'Hello, $name!' |
Lua |
String Formatting |
string.format("Hello, %s!", name) |
Objective-C |
String Formatting with NSString |
[NSString stringWithFormat:@"Hello, %@!", name] |
Shell Scripts |
Interpolation (Double-Quoted Strings) |
"Hello, $name!" |
R |
sprintf |
sprintf("Hello, %s!", name) |
Thank you!
-
Mar 17th, 2025, 07:01 AM
#2
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
I'm using something simple like this:
Code:
Public Function Printf(ByVal sText As String, ParamArray A() As Variant) As String
Const LNG_PRIVATE As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim lIdx As Long
For lIdx = UBound(A) To LBound(A) Step -1
sText = Replace(sText, "%" & (lIdx - LBound(A) + 1), Replace(A(lIdx), "%", ChrW$(LNG_PRIVATE)))
Next
Printf = Replace(sText, ChrW$(LNG_PRIVATE), "%")
End Function
. . . to format with %1, %2, %3, etc. placeholders like this:
> Debug.Print Printf("Hello, %1!", "World")
> Hello, World!
cheers,
</wqw>
-
Mar 17th, 2025, 01:15 PM
#3
Thread Starter
Junior Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
Hello wqweto sir,
missing so many things like format, special characters, escaping etc. its ok for learn but need more advance and complex like .Net and others
thank you so much for your reply and for your work
-
Mar 17th, 2025, 02:51 PM
#4
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
Here's my shot at an (optionally) recursive string formatted. Note that it's barely tested so there will be bugs, and it has not be optimized at all so it will probable perform poorly on large strings/lots of recursion.
Code:
Public Function RPrintF(ByVal p_String As String, ParamArray p_SubStrings() As Variant) As String
Const c_MaxRecurse As Long = 0 ' Set to 0 to disable "pseudo-recursive" substitutions,
' or set it to a reasonable number if you want "pseudo-recursive" replacement of %# substrings (with infinite loop protection).
Dim ii As Long
Dim l_Char As String
Dim l_InSubStringDef As Long
Dim l_InSubStringIndex As Long
Dim l_InSubStringFormatDef As Long
Dim l_RecurseCount As Long
Dim l_RecurseEnd As Long
Dim l_SubStringIndex As Long
Dim l_FormatString As String
Dim l_FormatStringDef As String
If (UBound(p_SubStrings) >= 0) And _
(Len(p_String) > 1) Or _
(InStr(1, p_String, "%") > 0) Then ' Only scan for substitutions if there is string content to scan,
' substrings to substitute,
' or if there is a % character (we may need to unescape even if there are no substitution strings)
ii = 1
Do While ii <= Len(p_String) + 1
If ii <= Len(p_String) Then
l_Char = Mid$(p_String, ii, 1)
Else
l_Char = vbNullString
End If
Select Case l_Char
Case "%"
' Check for start of substitution index marker
If l_InSubStringFormatDef = 0 Then
If (l_InSubStringDef > 0) And (l_InSubStringDef = ii - 1) Then
l_InSubStringDef = 0
p_String = Left$(p_String, ii - 1) & Mid$(p_String, ii + 1)
ii = ii - 1
Else
l_InSubStringDef = ii
End If
End If
Case "0" To "9"
' Check for substitution index #
If l_InSubStringFormatDef = 0 Then
If l_InSubStringDef Then
l_InSubStringIndex = ii
Else
l_InSubStringIndex = 0
End If
End If
Case Else
Select Case l_Char
Case "{"
' Check for start of format marker
If l_InSubStringFormatDef = 0 Then
If l_InSubStringIndex > 0 Then
l_InSubStringFormatDef = ii
End If
ElseIf l_InSubStringFormatDef = ii - 1 Then
l_InSubStringFormatDef = 0
End If
Case "}"
' Check for end of format marker
If l_InSubStringFormatDef > 0 Then
If Mid$(p_String, ii + 1, 1) <> "}" Then
' Found end of format string
l_FormatString = Mid$(p_String, l_InSubStringFormatDef + 1, ii - l_InSubStringFormatDef - 1)
l_FormatStringDef = "{" & l_FormatString & "}"
l_InSubStringFormatDef = 0
End If
End If
End Select
If (l_InSubStringIndex > 0) And _
(l_InSubStringDef > 0) And _
(l_InSubStringFormatDef = 0) Then
' Perform substitution
l_SubStringIndex = Mid$(p_String, l_InSubStringDef + 1, l_InSubStringIndex - l_InSubStringDef)
If l_SubStringIndex > 0 And l_SubStringIndex <= UBound(p_SubStrings) + 1 Then
l_RecurseEnd = l_InSubStringDef + Len(Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString))
l_RecurseCount = l_RecurseCount + 1
p_String = Mid$(p_String, 1, l_InSubStringDef - 1) & Replace$(p_String, "%" & l_SubStringIndex & l_FormatStringDef, Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString), l_InSubStringDef, 1)
ii = l_InSubStringDef - 1
End If
l_FormatString = vbNullString
l_FormatStringDef = vbNullString
l_InSubStringIndex = 0
l_InSubStringDef = 0
l_InSubStringFormatDef = 0
ElseIf l_InSubStringFormatDef = 0 Then
l_InSubStringDef = 0
End If
End Select
ii = ii + 1
' Infinite loop protection
If l_RecurseEnd > 0 Then If ii > l_RecurseEnd Then l_RecurseCount = 0: l_RecurseEnd = 0
If l_RecurseCount > c_MaxRecurse Then ii = l_RecurseEnd + 1
Loop
End If
RPrintF = p_String
End Function
Some notes:
- Substitution indexes are identified by a leading % character, and are 1-based. For example, RPrintF("Print %1", "Test") will output Print Test.
- You can add a format string after any substitution identifier by enclosing it in {braces}. Format strings can be anything that the VB6 Format function accepts. For example, RPrintF("Today is %1{Short Date}", Now) will return Today is 2025/03/17 (or whatever your system's short date format is)
- Substitution indexes do not have to be in order, so RPrintF("%2 %1", "B", "A") will work and return A B
- Recursion is supported with infinite loop protection if you set the c_MaxRecurse constant > 0. If you enable recursion, then something like this: RPrintF("Let's do %1.", "something %3", "terrible", "exciting") will return Let's do something exciting.
- If you mess up and try to do something like RPrintF("This is an infinite loop bomb %1", "Infinite Loop Bomb %2", "Infinite Loop Bomb %1") with recursion enabled you will get a crazy result, but you won't lock the program.
- You can escape special characters by doubling them up. For example %% will escape % so it doesn't get misinterpreted as the start of a sub-string identifier.
Hope it comes in handy, but please remember that the code has barely been tested so please report any problems here (or feel free to fix them and post the results here).
Last edited by jpbro; Mar 19th, 2025 at 02:45 PM.
Reason: Hardened against some bad inputs
-
Mar 17th, 2025, 05:32 PM
#5
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by silver-surfer
Hi! Seniors,
Please suggest good libs/code for VB6/VBA and should be competitive with following languages.
Just be aware that some of these features are compiler features. For example, no amount of VB6 code will give you string interpolation. That has to be implemented in the compiler.
-
Mar 17th, 2025, 06:23 PM
#6
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by Niya
Just be aware that some of these features are compiler features. For example, no amount of VB6 code will give you string interpolation. That has to be implemented in the compiler.
Good point, at best you'll get a "poor man's" string interpolation from VB6. IIRC twinBasic offers "real" string interpolation (or it's planned at least) if you OP wants to try a modern more VB6-like replacement to vb.Net.
-
Mar 18th, 2025, 03:49 AM
#7
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by silver-surfer
Hello wqweto sir,
missing so many things like format, special characters, escaping etc. its ok for learn but need more advance and complex like .Net and others
thank you so much for your reply and for your work
Do these outside your Printf usage e.g. sMsg = Printf("Current Date is %1", Format$(Date, FORMAT_SHORT_DATE)) where FORMAT_Xxx constants are project specific custom formats.
It's like when you need to change date format midway project (only dates, not currency, weight or volume) to something else so now someone will have to go find and fix all those String.Format specifiers and miss a bunch.
I prefer to keep *any* advanced stuff out of my trusty Printf so it can be used in critical sutiations (like error handling) with strong confidence it cannot fail. Advanced formatting, escaping, etc. is left to Format function and/or additional helpers like Pad, Trim, ToHex, ToBase64, ToUtf8, etc.
cheers,
</wqw>
Last edited by wqweto; Mar 18th, 2025 at 03:53 AM.
-
Mar 18th, 2025, 08:55 AM
#8
Fanatic Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by wqweto
I'm using something simple like this:
Code:
Public Function Printf(ByVal sText As String, ParamArray A() As Variant) As String
Const LNG_PRIVATE As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim lIdx As Long
For lIdx = UBound(A) To LBound(A) Step -1
sText = Replace(sText, "%" & (lIdx - LBound(A) + 1), Replace(A(lIdx), "%", ChrW$(LNG_PRIVATE)))
Next
Printf = Replace(sText, ChrW$(LNG_PRIVATE), "%")
End Function
. . . to format with %1, %2, %3, etc. placeholders like this:
> Debug.Print Printf("Hello, %1!", "World")
> Hello, World!
cheers,
</wqw>
This is a handy little routine. Here's a more robust version with some error handling and comments...
Code:
Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
' Define a constant for a Unicode Private Use Area character to temporarily replace % in arguments
Const TEMP_PERCENT As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim argIndex As Long ' Variable to loop through the argument array
' Enable error handling to catch and report any runtime issues
On Error GoTo ErrHandler
' Check if the input format string is empty; if so, return an empty string
If Len(formatString) = 0 Then
Printf = vbNullString
Exit Function
End If
' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
If UBound(args) < LBound(args) Then
Printf = formatString
Exit Function
End If
' Loop through the arguments in reverse order (from highest index to lowest)
For argIndex = UBound(args) To LBound(args) Step -1
' Replace the placeholder (e.g., %1, %2) in the format string with the current argument
' First, replace any % in the argument with TEMP_PERCENT to avoid conflicts
' Then, substitute the placeholder with the modified argument
formatString = Replace(formatString, "%" & (argIndex - LBound(args) + 1), _
Replace(args(argIndex), "%", ChrW$(TEMP_PERCENT)))
Next
' After all placeholders are replaced, restore any original % characters by
' replacing TEMP_PERCENT back to % in the final string
Printf = Replace(formatString, ChrW$(TEMP_PERCENT), "%")
' Exit the function normally
Exit Function
ErrHandler:
' If an error occurs, return an error message with the description
Printf = "[Error in Printf: " & Err.Description & "]"
End Function
-
Mar 18th, 2025, 09:48 AM
#9
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
@AAraya: Nice work, but you cannot use procedures with On Error ... statements while handling errors because Err object gets reset.
I mean if you have something like this
Code:
On Error GoTo ErrHandler
...
Exit Sub
ErrHandler:
Debug.Print Printf("Error: %1", Err.Description)
Debug.Print "Ooops, Err object is cleared at this point because Printf body has On Error statement"
LogError "MyModule::MyFunction"
End Sub
This is the only reason original procedure does not have error handling already.
cheers,
</wqw>
-
Mar 18th, 2025, 10:13 AM
#10
Fanatic Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
@wqweto - good point! Here's the function with the On Error functionality removed but with the validations for format string and ParamArray arguments.
Code:
Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
' Define a constant for a Unicode Private Use Area character to temporarily replace % in arguments
Const TEMP_PERCENT As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
Dim argIndex As Long ' Variable to loop through the argument array
' Check if the input format string is empty; if so, return an empty string
If Len(formatString) = 0 Then
Printf = vbNullString
Exit Function
End If
' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
If UBound(args) < LBound(args) Then
Printf = formatString
Exit Function
End If
' Loop through the arguments in reverse order (from highest index to lowest)
For argIndex = UBound(args) To LBound(args) Step -1
' Replace the placeholder (e.g., %1, %2) in the format string with the current argument
' First, replace any % in the argument with TEMP_PERCENT to avoid conflicts
' Then, substitute the placeholder with the modified argument
formatString = Replace(formatString, "%" & (argIndex - LBound(args) + 1), _
Replace(args(argIndex), "%", ChrW$(TEMP_PERCENT)))
Next
' After all placeholders are replaced, restore any original % characters by
' replacing TEMP_PERCENT back to % in the final string
Printf = Replace(formatString, ChrW$(TEMP_PERCENT), "%")
End Function
-
Mar 18th, 2025, 10:50 AM
#11
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
Btw, not sure ChatGPT added something useful here provided that it missed to include raising exception when formatString contains TEMP_PERCENT i.e. it couldn't figure out this is unsupported case and a much better/real input check than currently optimizing on empty formatString or missing placeholder replacements.
cheers,
</wqw>
-
Mar 18th, 2025, 11:40 AM
#12
Fanatic Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by wqweto
Btw, not sure ChatGPT added something useful here provided that it missed to include raising exception when formatString contains TEMP_PERCENT i.e. it couldn't figure out this is unsupported case and a much better/real input check than currently optimizing on empty formatString or missing placeholder replacements.
cheers,
</wqw>
That seems to be a rare edge case since TEMP_PERCENT is a Private Use Area character unlikely to appear in typical input. How likely do you consider this situation to be? To my mind empty strings and missing placeholders are more likely... But for bulletproof robustness I suppose that would be a good enhancement.
Rather than using a fixed/constant PUA character we could iterate the PUA until we find one that doesn't appear in the input string?
Code:
Private Function FindUnusedPUAChar(ByVal str As String) As Long
Dim codePoint As Long
For codePoint = &HE000& To &HF8FF& ' PUA range
If InStr(str, ChrW$(codePoint)) = 0 Then
FindUnusedPUAChar = codePoint
Exit Function
End If
Next
' If no unused character is found (extremely unlikely), raise an error
Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
End Function
Last edited by AAraya; Mar 18th, 2025 at 12:03 PM.
-
Mar 19th, 2025, 09:44 AM
#13
Fanatic Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
This started out to be an interesting thought experiment for me but then I realized that I was doing a simple version of this manually in a couple places in my code - mainly with resource strings that contained placeholders (%1, %2, etc). So I created a Printf function based on some of the things discussed in here. Here's my version, if anyone's interested.
It handles numbered placeholders like %1 as well as typed placeholders (%1s, %1d, %1) to support Integers and Floats passed in the param array.
Speaking of the ParamArray, this routine accepts both an array of parameters or separate parameters.
Code:
Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
'// Formats a string by replacing numbered placeholders (%1, %2, etc.) or typed placeholders (%1s, %1d, %1f)
'// with corresponding arguments, supporting strings, integers, and floats
'
'// Accepts both a ParamArray passed in and separate args:
'// Separate args example:
'// result = Printf("the %1 ate the %2", "dog", "cat")
'//
'// Single ParamArray example:
'// paramArrayTest = Array("dog", "cat")
'// result = Printf("the %1 ate the %2", paramArrayTest)
Dim TempPercentCharCode As Long ' Dynamically set to an unused PUA character to temporarily replace % in arguments
Dim argIndex As Long ' Variable to loop through the argument array
Dim placeholder As String ' Variable to store the current placeholder being processed
Dim actualArgs As Variant ' Added to hold unwrapped arguments from a nested ParamArray
Const PERCENT_CHAR As String = "%"
' Check if the input format string is empty; if so, return an empty string
If Len(formatString) = 0 Then
Printf = vbNullString
Exit Function
End If
' Find a character in the PUA range which does not appear in the formatString
' We're going to temporarily replace "%" in the arguments with this temp character
TempPercentCharCode = FindUnusedPUAChar(formatString)
' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
If UBound(args) < LBound(args) Then
Printf = formatString
Exit Function
End If
' Check if args is a nested array from another ParamArray
If UBound(args) = LBound(args) And Not IsEmpty(args) And VarType(args(LBound(args))) = vbArray + vbVariant Then
actualArgs = args(LBound(args)) ' Unwrap the nested array
Else
actualArgs = args ' Use args as-is
End If
' Check if the unwrapped arguments are empty; if so, return the format string unchanged
If UBound(actualArgs) < LBound(actualArgs) Then
Printf = formatString
Exit Function
End If
' Loop through the arguments in reverse order (from highest index to lowest)
For argIndex = UBound(actualArgs) To LBound(actualArgs) Step -1
' Calculate the base placeholder (e.g., %1, %2) for the current argument
placeholder = PERCENT_CHAR & (argIndex - LBound(actualArgs) + 1)
' Check for typed placeholders and apply appropriate type conversion
If InStr(formatString, placeholder & "s") > 0 Then
' Handle string specifier (%Ns): convert argument to string
formatString = Replace(formatString, placeholder & "s", _
Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
ElseIf InStr(formatString, placeholder & "d") > 0 Then
' Handle integer specifier (%Nd): truncate (Fix) to long integer
formatString = Replace(formatString, placeholder & "d", _
Replace(CStr(Fix(actualArgs(argIndex))), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
ElseIf InStr(formatString, placeholder & "f") > 0 Then
' Handle float specifier (%Nf): convert argument to double with default formatting
formatString = Replace(formatString, placeholder & "f", _
Replace(CDbl(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
Else
' Default case: replace plain placeholder (e.g., %1) with argument as-is
formatString = Replace(formatString, placeholder, _
Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
End If
Next
' After all placeholders are replaced, restore any original % characters by
' replacing TempPercentCharCode back to % in the final string
Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
End Function
' Helper function to find an unused PUA character
Private Function FindUnusedPUAChar(ByVal str As String) As Long
Dim codePoint As Long
For codePoint = &HE000& To &HF8FF& ' PUA range
If InStr(str, ChrW$(codePoint)) = 0 Then
FindUnusedPUAChar = codePoint
Exit Function
End If
Next
' If no unused character is found (extremely unlikely), raise an error
Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
End Function
Last edited by AAraya; Mar 19th, 2025 at 10:46 AM.
-
Mar 19th, 2025, 09:58 AM
#14
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by jpbro
Good point, at best you'll get a "poor man's" string interpolation from VB6.
Interesting you should mention this. Before it was added to .Net it was available in a poor man's version as a normal function call. The compiler implements string interpolation by simply replacing the string interpolation syntax with a call to this function.
-
Mar 22nd, 2025, 06:01 AM
#15
Thread Starter
Junior Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
-
Mar 22nd, 2025, 06:04 AM
#16
Thread Starter
Junior Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
hard work always return seems competitive
-
Mar 22nd, 2025, 06:07 AM
#17
Thread Starter
Junior Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
 Originally Posted by jpbro
Here's my shot at an (optionally) recursive string formatted. Note that it's barely tested so there will be bugs, and it has not be optimized at all so it will probable perform poorly on large strings/lots of recursion.
Code:
Public Function RPrintF(ByVal p_String As String, ParamArray p_SubStrings() As Variant) As String
Const c_MaxRecurse As Long = 0 ' Set to 0 to disable "pseudo-recursive" substitutions,
' or set it to a reasonable number if you want "pseudo-recursive" replacement of %# substrings (with infinite loop protection).
Dim ii As Long
Dim l_Char As String
Dim l_InSubStringDef As Long
Dim l_InSubStringIndex As Long
Dim l_InSubStringFormatDef As Long
Dim l_RecurseCount As Long
Dim l_RecurseEnd As Long
Dim l_SubStringIndex As Long
Dim l_FormatString As String
Dim l_FormatStringDef As String
If (UBound(p_SubStrings) >= 0) And _
(Len(p_String) > 1) Or _
(InStr(1, p_String, "%") > 0) Then ' Only scan for substitutions if there is string content to scan,
' substrings to substitute,
' or if there is a % character (we may need to unescape even if there are no substitution strings)
ii = 1
Do While ii <= Len(p_String) + 1
If ii <= Len(p_String) Then
l_Char = Mid$(p_String, ii, 1)
Else
l_Char = vbNullString
End If
Select Case l_Char
Case "%"
' Check for start of substitution index marker
If l_InSubStringFormatDef = 0 Then
If (l_InSubStringDef > 0) And (l_InSubStringDef = ii - 1) Then
l_InSubStringDef = 0
p_String = Left$(p_String, ii - 1) & Mid$(p_String, ii + 1)
ii = ii - 1
Else
l_InSubStringDef = ii
End If
End If
Case "0" To "9"
' Check for substitution index #
If l_InSubStringFormatDef = 0 Then
If l_InSubStringDef Then
l_InSubStringIndex = ii
Else
l_InSubStringIndex = 0
End If
End If
Case Else
Select Case l_Char
Case "{"
' Check for start of format marker
If l_InSubStringFormatDef = 0 Then
If l_InSubStringIndex > 0 Then
l_InSubStringFormatDef = ii
End If
ElseIf l_InSubStringFormatDef = ii - 1 Then
l_InSubStringFormatDef = 0
End If
Case "}"
' Check for end of format marker
If l_InSubStringFormatDef > 0 Then
If Mid$(p_String, ii + 1, 1) <> "}" Then
' Found end of format string
l_FormatString = Mid$(p_String, l_InSubStringFormatDef + 1, ii - l_InSubStringFormatDef - 1)
l_FormatStringDef = "{" & l_FormatString & "}"
l_InSubStringFormatDef = 0
End If
End If
End Select
If (l_InSubStringIndex > 0) And _
(l_InSubStringDef > 0) And _
(l_InSubStringFormatDef = 0) Then
' Perform substitution
l_SubStringIndex = Mid$(p_String, l_InSubStringDef + 1, l_InSubStringIndex - l_InSubStringDef)
If l_SubStringIndex > 0 And l_SubStringIndex <= UBound(p_SubStrings) + 1 Then
l_RecurseEnd = l_InSubStringDef + Len(Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString))
l_RecurseCount = l_RecurseCount + 1
p_String = Mid$(p_String, 1, l_InSubStringDef - 1) & Replace$(p_String, "%" & l_SubStringIndex & l_FormatStringDef, Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString), l_InSubStringDef, 1)
ii = l_InSubStringDef - 1
End If
l_FormatString = vbNullString
l_FormatStringDef = vbNullString
l_InSubStringIndex = 0
l_InSubStringDef = 0
l_InSubStringFormatDef = 0
ElseIf l_InSubStringFormatDef = 0 Then
l_InSubStringDef = 0
End If
End Select
ii = ii + 1
' Infinite loop protection
If l_RecurseEnd > 0 Then If ii > l_RecurseEnd Then l_RecurseCount = 0: l_RecurseEnd = 0
If l_RecurseCount > c_MaxRecurse Then ii = l_RecurseEnd + 1
Loop
End If
RPrintF = p_String
End Function
Some notes:
- Substitution indexes are identified by a leading % character, and are 1-based. For example, RPrintF("Print %1", "Test") will output Print Test.
- You can add a format string after any substitution identifier by enclosing it in {braces}. Format strings can be anything that the VB6 Format function accepts. For example, RPrintF("Today is %1{Short Date}", Now) will return Today is 2025/03/17 (or whatever your system's short date format is)
- Substitution indexes do not have to be in order, so RPrintF("%2 %1", "B", "A") will work and return A B
- Recursion is supported with infinite loop protection if you set the c_MaxRecurse constant > 0. If you enable recursion, then something like this: RPrintF("Let's do %1.", "something %3", "terrible", "exciting") will return Let's do something exciting.
- If you mess up and try to do something like RPrintF("This is an infinite loop bomb %1", "Infinite Loop Bomb %2", "Infinite Loop Bomb %1") with recursion enabled you will get a crazy result, but you won't lock the program.
- You can escape special characters by doubling them up. For example %% will escape % so it doesn't get misinterpreted as the start of a sub-string identifier.
Hope it comes in handy, but please remember that the code has barely been tested so please report any problems here (or feel free to fix them and post the results here).
thank your for you valuable time and effort
-
Mar 22nd, 2025, 09:39 AM
#18
Fanatic Member
Re: String Interpolation, String Formatting, Composite Formatting, Template Literals
I like the support for custom format strings in jpbro's code. I've added that in my version. I'm also adding some unit tests which I created to test the new function. Some advantages of my approach are:
1. Can accept both separate arguments and a single ParamArray
2. Supports typed placeholders like %1s for strings, %1d for integers (with truncation), and %1f for floats
3. Temporarily replaces % in arguments with a dynamically chosen Private Use Area (PUA) character, preventing interference with placeholder detection
4. Reverse Order Processing: Mine processes arguments from the highest to the lowest index, avoiding issues with overlapping placeholders and ensuring reliable substitutions.
5. No Recursion: Mine processes placeholders in a single pass
If your use case involves complex, nested formatting where placeholders depend on other placeholders, then jbpro's RPrintf recursion would absolutely be an advantage, and would serve you better. But if your needs are simpler, my Printf’s lack of recursion is safer and faster.
Code:
Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
'// Formats a string by replacing numbered placeholders (%1, %2, etc.), typed placeholders (%1s, %1d, %1f),
'// or formatted placeholders (%1{format}) with corresponding arguments.
'// Supports %% for literal % and custom formatting via VB6 Format$.
'//
'// Accepts both a ParamArray passed in and separate args:
'// Separate args example:
'// result = Printf("the %1 ate the %2", "dog", "cat")
'//
'// Single ParamArray example:
'// paramArrayTest = Array("dog", "cat")
'// result = Printf("the %1 ate the %2", paramArrayTest)
Dim TempPercentCharCode As Long ' Dynamically set to an unused PUA character to temporarily replace % in arguments
Dim argIndex As Long ' Variable to loop through the argument array
Dim placeholder As String ' Variable to store the current placeholder being processed
Dim actualArgs As Variant ' Added to hold unwrapped arguments from a nested ParamArray
Dim formatStart As Long ' Position of '{' in %N{format}
Dim formatEnd As Long ' Position of '}' in %N{format}
Dim formatSpec As String ' Extracted format specifier from {format}
Const PERCENT_CHAR As String = "%"
' Check if the input format string is empty; if so, return an empty string
If Len(formatString) = 0 Then
Printf = vbNullString
Exit Function
End If
' Find a character in the PUA range which does not appear in the formatString
' We’ll temporarily replace % in arguments and escaped %% with this character
TempPercentCharCode = FindUnusedPUAChar(formatString)
' Preprocess %% to TempPercentCharCode to handle escaping
formatString = Replace(formatString, "%%", ChrW$(TempPercentCharCode))
' Check if no arguments were provided (empty ParamArray); if so, return the format string with TempPercentCharCode restored
If UBound(args) < LBound(args) Then
Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
Exit Function
End If
' Check if args is a nested array from another ParamArray
If UBound(args) = LBound(args) And Not IsEmpty(args) And VarType(args(LBound(args))) = vbArray + vbVariant Then
actualArgs = args(LBound(args)) ' Unwrap the nested array
Else
actualArgs = args ' Use args as-is
End If
' Check if the unwrapped arguments are empty; if so, return the format string with TempPercentCharCode restored
If UBound(actualArgs) < LBound(actualArgs) Then
Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
Exit Function
End If
' Loop through the arguments in reverse order (from highest index to lowest)
For argIndex = UBound(actualArgs) To LBound(actualArgs) Step -1
' Calculate the base placeholder (e.g., %1, %2) for the current argument
placeholder = PERCENT_CHAR & (argIndex - LBound(actualArgs) + 1)
' Check for formatted placeholder (e.g., %1{0.00})
formatStart = InStr(formatString, placeholder & "{")
If formatStart > 0 Then
formatEnd = InStr(formatStart + 1, formatString, "}")
If formatEnd > 0 Then
formatSpec = Mid$(formatString, formatStart + Len(placeholder) + 1, formatEnd - formatStart - Len(placeholder) - 1)
' Apply Format$ with the specified format
formatString = Replace(formatString, placeholder & "{" & formatSpec & "}", _
Replace(Format$(actualArgs(argIndex), formatSpec), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
End If
Else
' Check for typed placeholders and apply appropriate type conversion
If InStr(formatString, placeholder & "s") > 0 Then
' Handle string specifier (%Ns): convert argument to string
formatString = Replace(formatString, placeholder & "s", _
Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
ElseIf InStr(formatString, placeholder & "d") > 0 Then
' Handle integer specifier (%Nd): truncate (Fix) to long integer
formatString = Replace(formatString, placeholder & "d", _
Replace(CStr(Fix(actualArgs(argIndex))), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
ElseIf InStr(formatString, placeholder & "f") > 0 Then
' Handle float specifier (%Nf): convert argument to double with default formatting
formatString = Replace(formatString, placeholder & "f", _
Replace(CDbl(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
Else
' Default case: replace plain placeholder (e.g., %1) with argument as-is
formatString = Replace(formatString, placeholder, _
Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
End If
End If
Next
' After all placeholders are replaced, restore any original % characters by
' replacing TempPercentCharCode back to % in the final string
Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
End Function
' Helper function to find an unused PUA character
Private Function FindUnusedPUAChar(ByVal str As String) As Long
Dim codePoint As Long
For codePoint = &HE000& To &HF8FF& ' PUA range
If InStr(str, ChrW$(codePoint)) = 0 Then
FindUnusedPUAChar = codePoint
Exit Function
End If
Next
' If no unused character is found (extremely unlikely), raise an error
Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
End Function
Public Sub TestPrintfScenarios()
Dim result As String
Dim paramArrayTest As Variant
' Test 1: Separate Arguments with Plain Placeholders
Debug.Print "Test 1: Separate arguments with plain placeholders"
result = Printf("the %1 ate the %2", "dog", "cat")
Debug.Print "Result: " & result
Debug.Print "Expected: the dog ate the cat"
Debug.Print "Pass: " & (result = "the dog ate the cat")
Debug.Print "----------------------------------------"
' Test 2: Single ParamArray with Plain Placeholders
Debug.Print "Test 2: Single ParamArray with plain placeholders"
paramArrayTest = Array("dog", "cat")
result = Printf("the %1 ate the %2", paramArrayTest)
Debug.Print "Result: " & result
Debug.Print "Expected: the dog ate the cat"
Debug.Print "Pass: " & (result = "the dog ate the cat")
Debug.Print "----------------------------------------"
' Test 3: Type Specifiers (string, integer, float)
Debug.Print "Test 3: Type specifiers (%1s, %2d, %3f)"
result = Printf("Name: %1s, Age: %2d, Height: %3f", "Alice", 25.7, 5.9)
Debug.Print "Result: " & result
Debug.Print "Expected: Name: Alice, Age: 25, Height: 5.9"
Debug.Print "Pass: " & (result = "Name: Alice, Age: 25, Height: 5.9")
Debug.Print "----------------------------------------"
' Test 4: Empty Format String
Debug.Print "Test 4: Empty format string"
result = Printf("", "dog", "cat")
Debug.Print "Result: " & result
Debug.Print "Expected: (empty string)"
Debug.Print "Pass: " & (result = vbNullString)
Debug.Print "----------------------------------------"
' Test 5: No Arguments
Debug.Print "Test 5: No arguments"
result = Printf("the %1 ate the %2")
Debug.Print "Result: " & result
Debug.Print "Expected: the %1 ate the %2"
Debug.Print "Pass: " & (result = "the %1 ate the %2")
Debug.Print "----------------------------------------"
' Test 6: Mixed Types and Invalid Conversion
Debug.Print "Test 6: Mixed types with potential invalid conversion"
On Error Resume Next
result = Printf("Value: %1d, Text: %2s", "not_a_number", 42)
If Err.Number <> 0 Then
result = "Error: " & Err.Description
End If
On Error GoTo 0
Debug.Print "Result: " & result
Debug.Print "Expected: (error due to invalid integer conversion)"
Debug.Print "Pass: " & (InStr(result, "Error") > 0)
Debug.Print "----------------------------------------"
' Test 7: Preservation of % in Arguments
Debug.Print "Test 7: Preservation of % in arguments"
result = Printf("Discount: %1, Rate: %2", "50%", "25%")
Debug.Print "Result: " & result
Debug.Print "Expected: Discount: 50%, Rate: 25%"
Debug.Print "Pass: " & (result = "Discount: 50%, Rate: 25%")
Debug.Print "----------------------------------------"
' Test 8: Format String with Original TEMP_PERCENT (&HE1B6&)
Debug.Print "Test 8: Format string containing original TEMP_PERCENT"
result = Printf("Test" & ChrW$(&HE1B6&) & " %1", "value")
Debug.Print "Result: " & result
Debug.Print "Expected: Test" & ChrW$(&HE1B6&) & " value"
Debug.Print "Pass: " & (result = "Test" & ChrW$(&HE1B6&) & " value")
Debug.Print "----------------------------------------"
' Test 9: Custom Numeric Format Specifier
Debug.Print "Test 9: Custom Numeric Format Specifier"
result = Printf("Price: %1{0.00}, Count: %2{000}", 5.9, 7)
Debug.Print "Result: " & result
Debug.Print "Expected: Price: 5.90, Count: 007"
Debug.Print "Pass: " & (result = "Price: 5.90, Count: 007")
Debug.Print "----------------------------------------"
' Test 10: Custom Date Format Specifier
Debug.Print "Test 10: Custom Date Format Specifier"
result = Printf("Date: %1{mmmm d, yyyy}", #1/1/2023#)
Debug.Print "Result: " & result
Debug.Print "Expected: Date: January 1, 2023"
Debug.Print "Pass: " & (result = "Date: January 1, 2023")
Debug.Print "----------------------------------------"
' Test 11: Escaped %%
Debug.Print "Test 11: Escaped %%"
result = Printf("Literal %% with %1", "test")
Debug.Print "Result: " & result
Debug.Print "Expected: Literal % with test"
Debug.Print "Pass: " & (result = "Literal % with test")
Debug.Print "----------------------------------------"
End Sub
Last edited by AAraya; Mar 22nd, 2025 at 09:43 AM.
Tags for this Thread
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|