Line continuation not working in VBA for 2D Array construction - excel

In the following Subroutine, defining a two dimensional array doesn't seem to work with line continuation. TestArray1 initializes as expected, but when I add line continuation I get the message,
"Compile Error - Closing bracket missing".
(Actually I'm not sure of the exact wording in English, doing this in German. In German the error message is,
"Fehler beim Komilieren: Fehlende schliesende Klammer".
I'm sure the English is not far off.)
Why would this not work?
Sub TestArrays()
Dim TestArray1 As Variant, TestArray2 As Variant
TestArray1 = [{"1String1", "1String2", "1String3"; "2String1", "2String2", "2String3"; "3String1", "3String2", "3String3"}]
TestArray2 = [{"1String1", "1String2", "1String3"; _
"2String1", "2String2", "2String3"; _
"3String1", "3String2", "3String3"]}
End Sub

Don't use square brackets.
Square brackets in VBA DO NOT stand for "this is an array", even though it looks like it (if you're any familiar with JSON anyway), and even though it might work.
Square brackets in VBA stand for "this is an expression that the host application will be evaluating at run-time".
In other words, it's giving work to Excel's expression evaluation engine: it's not VBA, it's Excel. The syntax of whatever is inside a square-bracketed expression must be legal in Excel's formula bar1.
Use the Array standard VBA function to create an array in VBA:
TestArray1 = Array("1String1", "1String2", "1String3", "2String1", "2String2", "2String3", "3String1", "3String2", "3String3")
Split it up with a line continuation at any point between two strings:
TestArray1 = Array( _
"1String1", "1String2", _
"1String3", "2String1", _
"2String2", "2String3", _
"3String1", "3String2", _
"3String3")
Note that the inconsistent ; vs , separators are probably part of the problem: Excel formulas use your system's list separator character: that's the character you'll want to use in square-bracket expressions - but you don't need to do that, because you don't need any square-bracket expressions.
There is no syntax for inline-initializing a 2D array in VBA. Declare & size your array explicitly instead:
Dim my2D(1 To 10, 1 To 10)
my2D(1, 1) = "1string1"
'...
If you have that much hard-coded strings in your code, then you are coding data. Data belongs in storage, not in code. Read the data from a worksheet, you'll get a 2D variant array for free, with a one-liner, without abusing language syntax, and if the data needs to change, then the code doesn't:
Dim my2D As Variant
my2D = sourceRange.Value
1 unless it's a VBA foreign identifier, in which case Excel doesn't get to evaluate it. Just don't use square bracket expressions, they're confusing.

Related

Replace non-printable characters with " (Inch sign) VBA Excel

I need to replace non-printable characters with " (Inch sign).
I tried to use excel clean function and other UDF functions, but it just remove and not replace.
Note: non-printable characters are highlighted in blue on the above photo and it's position is random on the cells.
this is a sample string file Link`
The expected correct output should be 12"x14" LPG . OUTLET OCT-SEP# process
In advance grateful for useful comments and answer.
As per my comment, you can try:
=SUBSTITUTE(A1,CHAR(25)&CHAR(25),CHAR(34))
Or the VBA pseudo-code:
[A1] = [A1].Replace(Chr(25) & Chr(25), Chr(34))
Where [A1] is the obvious placeholder for the range-object you would want to use with proper and absolute referencing.
With ms365 newest functions, we could also use:
=TEXTJOIN(CHAR(34),,TEXTSPLIT(A1,CHAR(25)))
You can use Regular Expressions within a UDF to create a flexible method to replace "bad" characters, when you don't know exactly what they are.
In the UDF below, I show two pattern options, but others are possible.
One is to replace all characters with a character code >127
the second is to replace all characters with a charcter code >255
Option Explicit
Function ReplaceBadChars(str As String, replWith As String) As String
Dim RE As Object
Set RE = CreateObject("Vbscript.Regexp")
With RE
.Pattern = "[\u0080-\uFFFF]" 'to replace all characters with code >127 or
'.Pattern = "[\u0100-\uFFFF]" 'to replace all characters with code >255
.Global = True
ReplaceBadChars = .Replace(str, replWith)
End With
End Function
On the worksheet you can use, for example:
=ReplaceBadChars(A1,"""")
Or you could use it in a macro if you wanted to process a column of data without adding an extra column.
Note: I am uncertain as to whether there might be an efficiency difference using a smaller negated character class (eg: [^\x00-\x79] instead of the character class I showed in the code. But if, as written, execution seems slow, I'd try this change)
You can try this :
Cells.Replace What:="[The caracter to replace]", Replacement:=""""

In Excel VBA, is there a way to interrogate the function wizard's parsing of arguments?

Consider an arbitrary Excel function with m required arguments and n optional arguments. For a near trivial example, say it is this summation of two to four items.
Function myUDF_Sum(req1, req2, Optional opt1 = 0, Optional opt2 = 0)
myUDF_Sum = req1 + req2 + opt1 + opt2
End Function
My objective is to parse out these arguments for placement in a custom form.
Excel's Function Wizard already does this:
Where for example I would want something like this:
So the question is, is there a way to leverage the parsing work already performed by the Excel Function Wizard? This would seem to be preferable to the alternative, regular expressions or some parser, and would return a list of strings s_1, ..., s_(m+n) representing the values or expressions that are used as the arguments in the function.
But I do not see a way to get access to the argument list in VBA for the function wizard.
I would suggest you to use a different approach, respectively tore register your function and use the standard Excel way of displaying it.
Please, use the next code to register it UDF:
Sub RegUDFMyFunc()
Dim strDescr As String
strDescr = "It sumarize whatever you input..." & vbLf _
& "It adds(myUDF_Sum:<req1 As Long>, <req2 As Long>, <Optional opt1>, <Optional opt2>)"
Application.MacroOptions Macro:="myUDF_Sum", Description:=strDescr, category:=9
End Sub
After that, you go in an Excel cell and start writing the function name. It will be proposed by the functions wizard. You will select its name and please, press fx sign on the formula bar. Your function will appear exactly as you need...
In order to un-register it, please use the next code:
Sub UnregUDFx()
Application.MacroOptions Macro:="myUDF_Sum", Description:=Empty, category:=Empty
End Sub

How do I extract a series of numbers along with a single letter followed by another series of numbers?

The problem that I'm facing is that I have an entire column that has text separated by _ that contains pixel size that I want to be able to extract but currently can't. For example:
A
Example_Number_320x50_fifty_five
Example_Number_One_300x250_hundred
Example_Number_two_fifty_728x49
I have tried using Substitute function to grab the numbers which works but only grabs the numbers when I need something like: 320x50 instead I'm getting 0, as I'm not sure how to exactly extract something like this. If it was consistent I could easily do LEFT or RIGHT formula's to grab it but as you can see the data varies.
The result that I'm looking for is something along the lines of:
A | B
Example_Number_320x50_fifty_five | 320x50
Example_Number_One_300x250_hundred | 300x200
Example_Number_two_fifty_728x49 | 728x49
Any help would be much appreciated! If any further clarification is needed please let me know and I'll try to explain as best as I can!
-Maykid
I would probably use a Regular Expressions UDF to accomplish this.
First, open up the VBE by pressing Alt + F11.
Right-Click on VBAProject > Insert > Module
Then you can paste the following code in your module:
Option Explicit
Public Function getPixelDim(RawTextValue As String) As String
With CreateObject("VBScript.RegExp")
.Pattern = "\d+x\d+"
If .Test(RawTextValue) Then
getPixelDim = .Execute(RawTextValue)(0)
End If
End With
End Function
Back to your worksheet, you would use the following formula:
=getPixelDim(A1)
Looking at the pattern \d+x\d+, an escaped d (\d) refers to any digit, a + means one or more of \d, and the x is just a literal letter x. This is the pattern you want to capture as your function's return value.
Gosh, K Davis was just so fast! Here's an alternate method with similar concept.
Create a module and create a user defined function like so.
Public Function GetPixels(mycell As Range) As String
Dim Splitter As Variant
Dim ReturnValue As String
Splitter = Split(mycell.Text, "_")
For i = 0 To UBound(Splitter)
If IsNumeric(Mid(Splitter(i), 1, 1)) Then
ReturnValue = Splitter(i)
Exit For
End If
Next
GetPixels = ReturnValue
End Function
In your excel sheet, type in B1 the formula =GetPixels(A1) and you will get 320x50.
How do you create a user defined function?
Developer tab
Use this URL to add Developer tab if you don't have it: https://www.addintools.com/documents/excel/how-to-add-developer-tab.html
Click on the highlighted areas to get to Visual Basic for Applications (VBA) window.
Create module
Click Insert > Module and then type in the code.
Use the user defined function
Note how the user defined function is called.

VBA Special characters U+2264 and U+2265

I have a frustrating problem. I have a string containg other characters that are not in this list (check link). My string represents a SQL Query.
This is an example of what my string can contain: INSERT INTO test (description) VALUES ('≤ ≥ >= <=')
When I check the database, the row is inserted successfully, but the characters "≤" and "≥" are replaced with "=" character.
In the database the string in description column looks like "= = >= <=".
For the most characters I can get a character code. I googled a character code for those two symbols, but I didn't find one. My goal is to check if my string contains this two characters , and afterwards replace them with ">=" and "<="
===Later Edit===
I have tried to check every character in a for loop;
tmp = Mid$(str, i, 1)
tmp will have the value "=" when my for loop reaches the "≤" character, so Excel cannot read this "≤" character in a VB string, then when I'm checking for character code I get the code for "=" (Chr(61))
Are you able to figure out what the character codes for both "≤" and "≥" in your database character set are? if so then maybe try replacing both characters in your query string with chrw(character_code).
I have just tested something along the lines of what you are trying to do using Excel as my database - and it looks to work fine.
Edit: assuming you are still stuck and looking for assistance here - could you confirm what database you are working with, and any type information setting for the "description" field you are looking to insert your string into?
Edit2: I am not familiar with SQL server, but isn't your "description" field set up to be of a certain data type? if so what is it and does it support unicode characters? ncharvar, nchar seem to be examples of sql server data types that support Unicode.
It sounds like you may also want to try and add an "N" prefix to the value in your query string - see
Do I have use the prefix N in the "insert into" statement for unicode? &
how to insert unicode text to SQL Server from query window
Edit3: varchar won't qualify for proper rendering of Unicode - see here What is the difference between varchar and nvarchar?. Can you switch to nvarchar? as mentionned above, you may also want to prefix the values in your query string with 'N' for full effect
Edit4: I can't speak much more about sqlserver, but what you are looking at here is how VBA displays the character, not at how it actually stores it in memory - which is the bottom line. VBA won't display "≤" properly since it doesn't support the Unicode character set. However, it may - and it does - store the binary representation correctly.
For any evidence of this, just try and paste back the character to another cell in Excel from VBA, and you will retrieve the original character - or look at the binary representation in VBA:
Sub test()
Dim s As String
Dim B() As Byte
'8804 is "≤" character in Excel character set
s = ChrW(8804)
'Assign memory representation of s to byte array B
B = s
'This loop prints "100" and "34", respectively the low and high bytes of s coding in memory
'representing binary value 0010 0010 0110 0100 ie 8804
For i = LBound(B) To UBound(B)
Debug.Print B(i)
Next i
'This prints "=" because VBA can not render character code 8804 properly
Debug.Print s
End Sub
If I copy your text INSERT INTO test (description) VALUES ('≤ ≥ >= <=') and paste it into the VBA editor, it becomes INSERT INTO test (description) VALUES ('= = >= <=').
If I paste that text into a Excel cell or an Access table's text field, it pastes "correctly".
This seems to be a matter of character code supported, and I suggest you have a look at this SO question.
But where in you program does that string come from, since it cannot be typed in VBA ??
Edit: I jus gave it a try with the below code, and it works like a charm for transferring your exotic characters from the worksheet to a table !
Sub test1()
Dim db As Object, rs As Object, cn As Object
Set cn = CreateObject("DAO.DBEngine.120")
Set db = cn.OpenDatabase("P:\Database1.accdb")
Set rs = db.OpenRecordset("table1")
With rs
.addnew
.Fields(0) = Range("d5").Value
.Update
End With
End Sub

Excel Select Case?

i want to create the "cases" formula for excel to simulate Select case behavior (with multiple arguments and else optional).
If A1 and A2 are excel cells, this is the goal:
A1 Case: A2 Formula: A2 Result
5 cases({A1>5,"greather than 5"}, {A1<5, "less than 5"},{else,"equal to 5"}) equal to 5
Hi cases({A1="","there is nothing"},{else,A1}) Hi
1024 cases({5<A1<=10,10},{11<=A1<100,100},{A1>100,1000}) 1000
12 cases({A1=1 to 9, "digit"}, {A1=11|22|33|44|55|66|77|88|99, "11 multiple"}) (empty)
60 cases({A1=1 to 49|51 to 99,"not 50"}) not 50
If it could, It must accept excel formulas or vba code, to make an operation over the cell before take a case, i.g.
cases({len(A1)<7, "too short"},{else,"good length"})
If it could, it must accept to or more cells to evaluate, i.g.
if A2=A3=A4=A5=1 and A1=2, A6="one", A7="two"
cases(A1!=A2|A3|A4|A5, A6}, {else,A7}) will produce "two"
By the way, | means or, != means different
Any help?
I'm grateful.
What I could write was this:
Public Function arr(ParamArray args()) 'Your function, thanks
arr = args
End Function
Public Function cases(arg, arg2) 'I don't know how to do it better
With Application.WorksheetFunction
cases = .Choose(.Match(True, arg, 0), arg2)
End With
End Function
I call the function in this way
=cases(arr(A1>5, A1<5, A1=5),arr( "gt 5", "lt 5", "eq 5"))
And i can't get the goal, it just works for the first condition, A1>5.
I fixed it using a for, but i think it's not elegant like your suggestion:
Function selectCases(cases, actions)
For i = 1 To UBound(cases)
If cases(i) = True Then
selectCases = actions(i)
Exit Function
End If
Next
End Function
When i call the function:
=selectCases(arr(A1>5, A1<5, A1=5),arr( "gt 5", "lt 5", "eq 5"))
It works.
Thanks for all.
After work a little, finally i get a excel select case, closer what i want at first.
Function cases(ParamArray casesList())
'Check all arguments in list by pairs (case, action),
'case is 2n element
'action is 2n+1 element
'if 2n element is not a test or case, then it's like the "otherwise action"
For i = 0 To UBound(casesList) Step 2
'if case checks
If casesList(i) = True Then
'then take action
cases = casesList(i + 1)
Exit Function
ElseIf casesList(i) <> False Then
'when the element is not a case (a boolean value),
'then take the element.
'It works like else sentence
cases = casesList(i)
Exit Function
End If
Next
End Function
When A1=5 and I call:
=cases(A1>5, "gt 5",A1<5, "lt 5","eq 5")
It can be read in this way: When A1 greater than 5, then choose "gt 5", but when A1 less than 5, then choose "lt 5", otherwise choose "eq 5". After run it, It matches with "eq 5"
Thank you, it was exciting and truly educative!
O.K., there's no way at all to do exactly what you want. You can't use anything other than Excel syntax within a formula, so stuff like 'A1 = 1 to 9' is just impossible.
You could write a pretty elaborate VBA routine that took strings or something and parsed them, but that really amounts to designing and implementing a complete little language. And your "code" wouldn't play well with Excel. For example, if you called something like
=cases("{A1="""",""there is nothing""},{else,A1}")
(note the escaped quotes), Excel wouldn't update your A1 reference when it moved or the formula got copied. So let's discard the whole "syntax" option.
However, it turns out you can get much of the behavior I think you actually want with regular Excel formulas plus one tiny VBA UDF. First the UDF:
Public Function arr(ParamArray args())
arr = args
End Function
This lets us create an array from a set of arguments. Since the arguments can be expressions instead of just constants, we can call it from a formula like this:
=arr(A1=42, A1=99)
and get back an array of boolean values.
With that small UDF, you can now use regular formulas to "select cases". They would look like this:
=CHOOSE(MATCH(TRUE, arr(A1>5, A1<5, A1=5), 0), "gt 5", "lt 5", "eq 5")
What's going on is that 'arr' returns a boolean array, 'MATCH' finds the position of the first TRUE, and 'CHOOSE' returns the corresponding "case".
You can emulate an "else" clause by wrapping the whole thing in 'IFERROR':
=IFERROR(CHOOSE(MATCH(TRUE, arr(A1>5, A1<5), 0), "gt 5", "lt 5"), "eq 5")
If that is too verbose for you, you can always write another VBA UDF that would bring the MATCH, CHOOSE, etc. inside, and call it like this:
=cases(arr(A1>5, A1<5, A1=5), "gt 5", "lt 5", "eq 5")
That's not far off from your proposed syntax, and much, much simpler.
EDIT:
I see you've already come up with a (good) solution that is closer to what you really want, but I thought I'd add this anyway, since my statement above about bringing MATCH, CHOOSE, etc. inside the UDF made it look easier thatn it really is.
So, here is a 'cases' UDF:
Public Function cases(caseCondResults, ParamArray caseValues())
On Error GoTo EH
Dim resOfMatch
resOfMatch = Application.Match(True, caseCondResults, 0)
If IsError(resOfMatch) Then
cases = resOfMatch
Else
Call assign(cases, caseValues(LBound(caseValues) + resOfMatch - 1))
End If
Exit Function
EH:
cases = CVErr(xlValue)
End Function
It uses a little helper routine, 'assign':
Public Sub assign(ByRef lhs, rhs)
If IsObject(rhs) Then
Set lhs = rhs
Else
lhs = rhs
End If
End Sub
The 'assign' routine just makes it easier to deal with the fact that users can call UDFs with either values or range references. Since we want our 'cases' UDF to work like Excel's 'CHOOSE', we'd like to return back references when necessary.
Basically, within the new 'cases' UDF, we do the "choose" part ourselves by indexing into the param array of case values. I slapped an error handler on there so basic stuff like a mismatch between case condition results and case values will result in a return value of #VALUE!. You would probably add more checks in a real function, like making sure the condition results were booleans, etc.
I'm glad you reached an even better solution for yourself, though! This has been interesting.
MORE ABOUT 'assign':
In response to your comment, here is more about why that is part of my answer. VBA uses a different syntax for assigning an object to a variable than it does for assigning a plain value. Look at the VBA help or see this stackoverflow question and others like it: What does the keyword Set actually do in VBA?
This matters because, when you call a VBA function from an Excel formula, the parameters can be objects of type Range, in addition to numbers, strings, booleans, errors, and arrays. (See Can an Excel VBA UDF called from the worksheet ever be passed an instance of any Excel VBA object model class other than 'Range'?)
Range references are what you describe using Excel syntax like A1:Q42. When you pass one to an Excel UDF as a parameter, it shows up as a Range object. If you want to return a Range object from the UDF, you have to do it explicitly with the VBA 'Set' keyword. If you don't use 'Set', Excel will instead take the value contained within the Range and return that. Most of the time this doesn't matter, but sometimes you want the actual range, like when you've got a named formula that must evaluate to a range because it's used as the source for a validation list.

Resources