Translate formula quotation marks incl. replacements into VBA-readable formulae
I was inspired to write this post by the recent question of
formula substitution using a constant.
At the same time, the frequent problem emerged that quotation marks
within a formula string should be replaced by double quotation marks in order
to make them readable in VBA.
Practical use case
A practical use case is to copy a table formula directly from a SO website
and "translate" it into a string-readable format.
But how is this supposed to be done with VBA means, since the direct input of
such an incomplete formula string in a procedure code without manually
added double quotation marks would immediately lead to an error?
Another feature would be to make replacements at certain points within
a formula template, for example with a constant or even with several
numerically identifiable markers.
I found a quick & dirty solution (without error handling) by analyzing a FormulaContainer procedure containing
exclusively outcommented formulae as these would allow any prior direct code input.
In order to distinguish them from the usual commentaries,
I decided with a heavy heart to use the Rem prefix (i.e. Remark) as an alternative, which we may still be familiar with from ancient Basic times.
My intention is not to show a perfect solution, but to stimulate further solutions
by demonstrating a possible way.
Question
Are there other work arounds allowing to copy tabular formulae with quotation marks directly and as possible replacement pattern into VBA procedures?
///////////////////////////////////
Main function QuickFormula()
References a FormulaContainer procedure containing exclusively formulae with Rem prefixes, such as e.g.
Sub FormulaContainer()
Rem =....
Rem =....
End Sub
This allows formula inputs with quotation marks similar to tabular cell inputs;
furthermore these inputs may contain string identifiers facilitating wanted replacements.
Option Explicit
'Site: https://stackoverflow.com/questions/70399681/how-many-quotes-to-put-around-a-formula-that-is-sending-an-empty-string
'Auth: https://stackoverflow.com/users/6460297/t-m
Function QuickFormula(ByVal no As Long, ParamArray repl() As Variant) As String
'Purp: - change indicated code line in FormulaContainer to code readable string and
' - replace enumerated identifiers with given value(s)
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'1) get REMark code line indicated by ordinal argument no
QuickFormula = getCodeLine("modFormula", "FormulaContainer", no)
'2a)replace "#" identifyer(s) with constant repl value
If Not IsArray(repl(0)) Then
QuickFormula = Replace(QuickFormula, "{1}", "#")
QuickFormula = Replace(QuickFormula, "#", repl(0))
If Len(QuickFormula) = 0 Then QuickFormula = "Error NA!"
Debug.Print no & " ~~> " & Chr(34) & QuickFormula & Chr(34)
Exit Function
End If
'2b)replace 1-based "{i}" identifiers by ParamArray values
Dim i As Long
For i = LBound(repl(0)) To UBound(repl(0))
QuickFormula = Replace(QuickFormula, "{" & i + 1 & "}", repl(0)(i))
Next
'3) optional display in immediate window
Debug.Print no & " ~~> " & Chr(34) & QuickFormula & Chr(34)
End Function
Help function getCodeLine()
Gets a given code line of the indicated procedure
Function getCodeLine(ByVal ModuleName As String, ByVal ProcedureName As String, Optional ByVal no As Long = 1) As String
'Purp: return a code line in given procedure containing "Rem "
'Note: assumes no line breaks; needs a library reference to
' "Microsoft Visual Basic for Applications Extensibility 5.3"
Const SEARCH As String = "Rem =", QUOT As String = """"
'1) set project
Dim VBProj As Object
Set VBProj = ThisWorkbook.VBProject
If VBProj.Protection = vbext_pp_locked Then Exit Function ' escape locked projects
'2) set component
Dim VBComp As Object
Set VBComp = VBProj.VBComponents(ModuleName)
Dim pk As vbext_ProcKind
'3) get no + 3 top code line(s)
With VBComp.CodeModule
'a)count procedure header lines
Dim HeaderCount As Long: HeaderCount = .ProcBodyLine(ProcedureName, pk) - .ProcStartLine(ProcedureName, pk)
'b) get procedure code
Dim codelines
'codelines = Split(.Lines(.ProcBodyLine(ProcedureName, pk), .ProcCountLines(ProcedureName, pk) - HeaderCount), vbNewLine)
codelines = Split(.Lines(.ProcBodyLine(ProcedureName, pk), no + 1), vbNewLine)
'c) filter code lines containing "Rem" entries
codelines = Filter(codelines, SEARCH, True)
End With
'4) return (existing) codeline no
If no - 1 > UBound(codelines) Then Exit Function ' check existance
getCodeLine = Replace(Replace(codelines(no - 1), QUOT, String(2, QUOT)), "Rem =", "=")
End Function
Example call
References all three formulae in the FormulaContainer (including an example of a non-existing number):
Sub EnterFormula()
With Sheet1.Range("X1") ' << change to any wanted target range
.Offset(1).Formula2 = QuickFormula(1, 6)
.Offset(2).Formula2 = QuickFormula(2, Array(10, 20, 30))
'two single argument inputs with same result
.Offset(3).Formula2 = QuickFormula(3, Array(17))
.Offset(4).Formula2 = QuickFormula(3, 17)
'not existing formula number in Rem code container
.Offset(5).Formula2 = QuickFormula(333, 17)
End With
End Sub
Example FormulaContainer
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'Purp: formula container to be adjusted to code readable strings
'Note: Insert only Formulae starting with "Rem "-prefix!
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
' # identifies constant replacement(s)
' {i} stands for enumerated replacements {1},{2}..{n}
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sub FormulaContainer()
Rem =IF($V#>0,IF($G#>$S#,($S#-$H#)*$K#+$Y#,($G#-$H#)*$K#+$Y#),"")
Rem =A{1}*B{3}+C{2}
Rem =A{1}+100
End Sub
Example output in immediate window
1 ~~> "=IF($V6>0,IF($G6>$S6,($S6-$H6)*$K6+$Y6,($G6-$H6)*$K6+$Y6),"""")"
2 ~~> "=A10*B30+C20"
3 ~~> "=A17+100"
3 ~~> "=A17+100"
333 ~~> "Error NA!"
Keep it simple stupid
Assuming either the currently selected cell formula or a textbox input, a simple Userform might act as a formula translator into a line of VBA code:
Basic Userform code
Needed: TextBox1, TextBox2, CommandButton1
Option Explicit
Private Sub CommandButton1_Click()
'Purp: Redouble inside quotation marks
Const Quot As String = """"
Dim assignTo As String
assignTo = "ws.Range(""" & Selection.Address(False, False) & """).Formula2 = "
Me.TextBox2.Text = assignTo & Quot & Replace(Me.TextBox1.Text, Quot, String(2, Quot)) & Quot
End Sub
Private Sub UserForm_Initialize()
'Purp: assume active formula as wanted input
Me.TextBox1 = Selection.Formula2
End Sub
Private Sub UserForm_Layout()
'Purp: example layout textboxes
'a) define textboxes
Dim textboxes() As String
textboxes = Split("Textbox1,Textbox2", ",")
'b) format
Dim i As Long
For i = 0 To UBound(textboxes)
With Me.Controls(textboxes(i))
.Font.Name = "Courier New"
.Font.Size = 12
.MultiLine = True
.EnterKeyBehavior = True
End With
Next i
End Sub
Possible extensions
Of course you might add an insertion routine (inserting e.g. {} brackets) as well as some replacement procedures like in my workaround above.
Just for fun, a basic insertion routine here:
Private Sub CommandButton2_Click()
'Purp: Insert brackets {}
With Me.TextBox1
.SetFocus
If InsertAtCursor("{}", Me.TextBox1) Then
.SelStart = .SelStart - 1
End If
End With
End Sub
Public Function InsertAtCursor(s As String, ctrl As MSForms.Control, Optional ErrMsg As String) As Boolean
'Purpose: Insert the characters at the cursor in the active control.
'Site: http://allenbrowne.com/func-InsertChar.html
'Return: True if characters were inserted.
'Arguments: s = the character(s) you want inserted at the cursor.
' ErrMsg = string to append any error messages to.
'Note: Control must have focus.
On Error GoTo Err_Handler
Dim prior As String 'Text before the cursor.
Dim after As String 'Text after the cursor.
Dim cnt As Long 'Number of characters
Dim iSelStart As Long 'Where cursor is.
Dim txt As String 'text with LineFeeds only
If s <> vbNullString Then
With ctrl ' --> UserForm Control
txt = Replace(.Text, vbCrLf, vbLf) ' LineFeeds only (MultiLine)
If .Enabled And Not .Locked Then
cnt = Len(txt) ' Zählung ohne vbCr's !
'SelStart can't cope with more than 32k characters.
If cnt <= 32767& - Len(s) Then
'Remember characters before cursor.
iSelStart = .SelStart
If iSelStart > 1 Then
prior = Left$(txt, iSelStart)
End If
'Remember characters after selection.
If iSelStart + .SelLength < cnt Then
after = Mid$(txt, iSelStart + .SelLength + 1) ' OP:2
End If
'Assign prior characters, new ones, and later ones.
.value = prior & s & after
'Put the cursor back where it as, after the new ones.
.SelStart = iSelStart + Len(s)
'Return True on success
InsertAtCursor = True
End If
End If
End With
End If
Exit_Handler:
Exit Function
Err_Handler:
Debug.Print Err.Number, Err.Description
Select Case Err.Number
Case 438&, 2135&, 2144& 'Object doesn't support this property. Property is read-only. Wrong data type.
ErrMsg = ErrMsg & "You cannot insert text here." & vbCrLf
Case 2474&, 2185& 'No active control. Control doesn't have focus.
ErrMsg = ErrMsg & "Cannot determine which control to insert the characters into." & vbCrLf
Case Else
ErrMsg = ErrMsg & "Error " & Err.Number & ": " & Err.Description & vbCrLf
End Select
Resume Exit_Handler
End Function
I have copied a code which runs a string in html format into the I.E. web browser, which converts it to rich text, then copies it back into the cell that the code was run on. I've included the code below.
However, I want the ability to run this sub as a function for other users so they will never have to access the developer tab. I'm stuck on how to convert this sub into a function so it would be great if someone could help me out.
Sub Sample()
Dim Ie As Object
Set Ie = CreateObject("InternetExplorer.Application")
With Ie
.Visible = False
.Navigate "about:blank"
.document.body.InnerHTML = Sheets("Sheet1").Range("A1").Value
.ExecWB 17, 0: .ExecWB 12, 2
ActiveSheet.Paste Destination:=Sheets("Sheet1").Range("A1")
.Quit
End With
End Sub
Thanks for all the responses.
for example, in cell A1 I have '< html > this is < b > a custom text < / b>'. I want to be able to write in cell A2 '=sample(A1)', and for the text in A1 to be converted into formatted text, ie. 'this is a custom text' by the code I've included. The cell A2 would have the converted text in.
This UDF addresses the use case given in the question. The function uses a regular expression to parse out the text to be bolded and then leverages Mark.R's code from a few years ago to translate the character values to the equivalent unicode bold character values. This code only handles the <b> tag but it could easily be extended to handle the <i> tag.
Option Explicit
Function EmboldenText(ByVal source As Range) As String
Dim re As VBScript_RegExp_55.RegExp
Dim m As VBScript_RegExp_55.Match
Dim strIn As String
Dim strout As String
Dim start As Integer
Set re = New VBScript_RegExp_55.RegExp
re.Pattern = "<b>(.+?)</b>"
re.Global = True
strIn = source.Value
strout = ""
start = 1
For Each m In re.Execute(strIn)
strout = strout & Mid(strIn, start, m.FirstIndex - start + 1) & _
Bold(m.SubMatches(0))
start = m.FirstIndex + m.Length + 1
Next m
EmboldenText = strout & Mid(strIn, start)
End Function
You will need to add this library to your references if you want to try this solution.
I am trying to read everything in a worksheet in excel and assign it to a String variable.
Dim TextTxt as String
'looping through worksheets to find worksheet "Card"
If Sheet.name = "Card" Then
ThisWorkbook.Sheets("Card").Copy
End If
Above code copies the sheet, but how can I assign this to a text variable called "TextTxt". This TextTxt variable will be used to split and find the necessary strings later in this coding.
This allows you to grab all the data on the sheet and store it in an object. This does not yet solve the output to a textfile part of your question.
Updated answer
Sub Test()
Dim TextTxt As Range
Dim c As Range
Dim sOutput As String
'looping through worksheets to find worksheet "Card"
If ActiveSheet.Name = "Card" Then
Set TextTxt = ThisWorkbook.Sheets("Card").UsedRange
For Each c In TextTxt
sOutput = c.Value & "," & sOutput
Next c
End If
MsgBox (sOutput)
End Sub
I have a column in a spreadsheet.
The format of the data in each cell is aa-0001-xx.
I need to examine the whole column to find the highest value of the sequence number. this would be the substring from column4 thru column7.
I can find the sequence number using Mid(ActiveWorkbook.Sheets("Sheet1").Range("B2:B2"), 4, 4)
But I need to find the max sequence in the whole column.
I am doing this in VBA.
Any help would be appreciated.
Here is my code so far:
Private Sub CommandButton1_Click()
Dim sQuoteNumber As String
Dim sFileName As String
Dim sPathName As String
Dim checkit As String
'Log the Quote
'First, open the log file and determine the next sequential log number.
sPathName = "C:\Users\Rich\Documents\Bryan\BigProject\"
sFileName = "QuoteLog2016.xlsx"
ControlFile = ActiveWorkbook.Name
Workbooks.Open Filename:=sPathName & sFileName
'Create the new Quote Number
checkit = Mid(ActiveWorkbook.Sheets("Sheet1").Range("B2:B2"), 4, 4) ' This is a temp test line
If Mid(ActiveWorkbook.Sheets("Sheet1").Range("B2:B2"), 4, 4) = "" Then
sQuoteNumber = "16-0001"
Else
'find the biggest number
'Here I was looking to like pass the mid function to a Max function of some sort.
sQuoteNumber = "16-0002"
End If
MsgBox ("The new Quote Number is: " + sQuoteNumber)
'Save the log entry
Workbooks(sFileName).Close
All of the comments made to your answer would work well for you. It's also true that there's no evidence in your code at having attempted something, however rudimentary, and this is why answers to a rather trivial task are not forthcoming for you. Perhaps, in future, have a go at some kind of solution ( even if it feels more guesswork than anything) and people on this site will be much more supportive of you.
To set you on your way, you could make use of the Split() function which converts a String into a String array, separated by a nominated value - in the case of your quotations, you could use "-" as your separator. This might be easier than your Mid function and will deal with the case of different sized quotations.
The code below will get you started but you'd want some error handling in there to test, for example, that each cell splits appropriately or that any cells aren't blank. I'll leave all of that to you.
Option Explicit
Private mLastQuote As Long
Public Sub Test()
Initialise 'call this routine just once at the start of your project
MsgBox GetNextQuote(16) 'use the GetNextQuote() function to get next number
MsgBox GetNextQuote(16)
MsgBox GetNextQuote(16)
End Sub
Private Function GetNextQuote(prefix As Integer) As String
mLastQuote = mLastQuote + 1
GetNextQuote = CStr(prefix) & "-" & _
Format(mLastQuote, "000#")
End Function
Private Sub Initialise()
Const PATH_NAME As String = "C:\Users\Rich\Documents\Bryan\BigProject\"
Const FILE_NAME As String = "QuoteLog2016.xlsx"
Const QUOTE_COL As String = "B"
Dim wb As Workbook
Dim ws As Worksheet
Dim v As Variant
Dim r As Long
Dim parts() As String
Dim num As Long
Application.ScreenUpdating = False
Set wb = Workbooks.Open(PATH_NAME & FILE_NAME, True, True)
Set ws = wb.Worksheets("Sheet1")
'Read quote values into variant array
With ws
v = .Range(.Cells(2, QUOTE_COL), _
.Cells(.Rows.Count, QUOTE_COL).End(xlUp)) _
.Value2
End With
'Find max quote
For r = 1 To UBound(v, 1)
parts = Split(v(r, 1), "-") 'splits quote into 3 parts
num = CLng(parts(1)) 'index (1) is the middle part
If num > mLastQuote Then mLastQuote = num
Next
wb.Close False
Application.ScreenUpdating = True
End Sub
Following is the statement
Performance;#Recruiting;#Culture and values;#Community Involvement &
Volunteerism;/Talent Development;#Workplace
I want each value present after the ;# sign to be paste in a new cell? How do i do it?
I've not used VBA for some time, but this should get you started at least:
Private Sub ProcessStr()
Dim strTest As String
Dim strArray() As String
Dim i As Integer
strTest = "YOUR STRING"
strArray = Split(strTest, ";")
For i = LBound(strArray) To UBound(strArray)
// REMOVE # SIGN HERE ?
// DO SOMETHING WITH THE VALUES
// strArray(i) - CONTAINS EACH VALUE
// PLACE IN INDIVIDUAL CELLS
Next
End Sub
Hope this helps!
dim arrString() as string
dim strInput as string
dim i as integer
strInput = "Performance;#Recruiting;#Culture and values;#Community Involvement &
Volunteerism;/Talent Development;#Workplace"
arrStrings = strings.split(strInput, ";#")
for i = 1 to ubound(arrstrings)
cells(i, 1) = arrstrings(i)
next i