Looping through array and capturing substring between two specific words - arrays

I paste data from elsewhere to a userform.
For example (the copied string)
Clinical: history of heart disease
Labs: elevated cholesterol on 8Aug
Meds: just started cholesterol medication
Supplements: none
Allergies: none
Activity: recently started going to YMCA 3x/wk (elliptical and some weight lifting
I want to paste the above string into textbox1.
The string should then be split into appropriate headings on textboxes 2 to 7 on the same userform.
In textbox 2, I want everything between "Clinical:" and "Labs:"
"history of heart disease" without the headings.
If “Labs:” is not present, I want everything between Clinical: and Meds (or next heading)
At this point, I think a loop to repeat this process but for the next items
(e.g. texbox 3 = everything between Labs: and Meds – or next heading; Textbox4 = everything between Meds: and Supplements) – or next heading; etc.
Private Sub CommandButton1_Click()
Dim strnames(1 To 6) As String
strnames(1) = "Clinical: "
strnames(2) = "Labs: "
strnames(3) = "Meds: "
strnames(4) = "Supps: "
strnames(5) = "Allergies: "
strnames(6) = "Activity: "
strnames(7) = "NFPE: "
Dim check As Integer
str1 = TextBox1
x = 1
For box = 1 To 6
If InStr(TextBox1.Text, strnames(1)) > 0 Then
str2 = SuperMid(str1, strnames(x), strnames(x + 1))
TextBox2 = str2
End If
If InStr(TextBox1.Text, strnames(1)) = 0 Then
TextBox2 = "none"
End If
Next box
End sub
This is the code that I have been using (from wellsr.com) to capture the data between word1 and word2 of the array. The problem occurs when a word in the array is not present at which point it adds all of the text following the first word.
Public Function SuperMid(ByVal strMain As String, str1 As String, str2 As String,
Optional reverse As Boolean) As String
Dim i As Integer, j As Integer, temp As Variant
On Error GoTo errhandler:
If reverse = True Then
i = InStrRev(strMain, str1)
j = InStrRev(strMain, str2)
If Abs(j - i) < Len(str1) Then j = InStrRev(strMain, str2, i)
If i = j Then 'try to search 2nd half of string for unique match
j = InStrRev(strMain, str2, i - 1)
End If
End If
If reverse = False Then
i = InStr(1, strMain, str1)
j = InStr(1, strMain, str2)
If Abs(j - i) < Len(str1) Then j = InStr(i + Len(str1), strMain, str2)
If i = j Then 'try to search 2nd half of string for unique match
j = InStr(i + 1, strMain, str2)
End If
End If
If i = 0 And j = 0 Then GoTo errhandler:
If j = 0 Then j = Len(strMain) + Len(str2) 'just to make it arbitrarily large
If i = 0 Then i = Len(strMain) + Len(str1) 'just to make it arbitrarily large
If i > j And j <> 0 Then 'swap order
temp = j
j = i
i = temp
temp = str2
str2 = str1
str1 = temp
End If
i = i + Len(str1)
SuperMid = Mid(strMain, i, j - i)
Exit Function
errhandler:
MsgBox "Error extracting strings. Check your input" & vbNewLine & vbNewLine & "Aborting", , "Strings not found"
End
End Function

Sometimes you need to add a little complication to make things easier. The code below may be of interest.
Option Explicit
' This code requires a reference to the Microsoft Scripting Runtime
Public Sub Test()
Dim myHistory As Scripting.Dictionary
Set myHistory = GetHistoryDictionary("Clinical: history of heart disease Labs: elevated cholesterol on 8AugMeds: just started cholesterol medication Supplements: none Allergies: none Activity: recently started going to YMCA 3x/wk (elliptical and some weight lifting)")
Debug.Print VBA.Join(myHistory.keys, vbCrLf)
Debug.Print VBA.Join(myHistory.Items, vbCrLf)
Debug.Print
If myHistory.Exists("Labs") Then
Debug.Print "The Lab report was: " & myHistory.Item("Labs")
End If
Debug.Print
If myHistory.Exists("Heamatology") Then
Debug.Print "The Heamatolofy report was: " & myHistory.Item("Heamatology")
Else
Debug.Print "The Heamtology report was: " & "Not Present"
End If
End Sub
Public Function GetHistoryDictionary(ByVal ipString As String) As Scripting.Dictionary
' Create an array of the labes in the input strings
Static myLabels As Variant
If VBA.IsEmpty(myLabels) Then
myLabels = Split("Clinical:,Labs:,Meds:,Supps:,Allergies:,Activity:,NFPE:", ",")
End If
' Add a character we can use as a separator with SPlit
Dim myLabel As Variant
For Each myLabel In myLabels
ipString = VBA.Replace(ipString, myLabel, "#" & myLabel)
Next
' remove characters until we have removed the first separator character
Do Until VBA.Left(ipString, 1) = "#"
ipString = VBA.Mid$(ipString, 2)
Loop
ipString = VBA.Mid$(ipString, 2)
'Get an array of Label/Message
Dim myItems As Variant
myItems = VBA.Split(ipString, "#")
'Split the label/message and put into a scripting.dictionary
Dim myHistory As Scripting.Dictionary
Set myHistory = New Scripting.Dictionary
Dim myItem As Variant
For Each myItem In myItems
Dim mySPlit As Variant
mySPlit = VBA.Split(myItem, ":")
myHistory.Add mySPlit(0), mySPlit(1)
Next
Set GetHistoryDictionary = myHistory
End Function

Building on your code:
First ensure you have Option Explicit at the top of all your modules as this will help pick out any simple errors.
In your UserForm you could have text boxes labeled TextBox1, TextBox2 etc. Then you could use this for the command button code:
Private Sub CommandButton1_Click()
Dim strnames(1 To 7) As String
strnames(1) = "Clinical: "
strnames(2) = "Labs: "
strnames(3) = "Meds: "
strnames(4) = "Supps: "
strnames(5) = "Allergies: "
strnames(6) = "Activity: "
strnames(7) = "NFPE: "
Dim str1 As String
str1 = TextBox1.Text
' It makes the code clearer if you are explicit about what you want
' from your text box - .Text (or .Value), even if VBA will
' give you its value if you don't specify it.
Dim str2 As String
Dim ctlControl As Control
Dim lngTextBoxNumber As Long
' You need to loop through all the controls on the form, and then
' determine which are the ones you want to alter. This assumes each
' textbox you are interested in is named in the form
' TextBox1, TextBox2 etc. To make code maintenance easier, I would
' probably put this kind of identification information on the
' controls' tag properties - that way if you rename the controls or
' you add a text box which is for something else, you won't break
' the code. You would then be reading this information off the
' .Tag property rather than .Name.
For Each ctlControl In Me.Controls
If Mid$(ctlControl.Name, 1, 7) = "TextBox" Then
lngTextBoxNumber = CLng(Mid$(ctlControl.Name, 8))
If lngTextBoxNumber > 1 And lngTextBoxNumber < UBound(strnames) Then
str2 = SuperMid(str1, strnames(lngTextBoxNumber), strnames(lngTextBoxNumber + 1))
If str2 = vbNullString Then
str2 = "none"
End If
ctlControl.Text = str2
End If
End If
Next ctlControl
End Sub
SuperMid seems to be quite an unforgiving function - as you have it, if it can't find the text before and after the text you are looking for, it will fail with an error: it might be better for it to return an empty string - otherwise your code will fail not all the strnames are present in your original string.
I altered the end of that function to look like this:
Exit Function
errhandler:
'MsgBox "Error extracting strings. Check your input" & vbNewLine & vbNewLine & "Aborting", , "Strings not found"
SuperMid = vbNullString
End Function
As it stands, your code would fail to pick up some of the information if items are left out, or had been entered in a different order: see freeflow's answer to avoid this.

I would skip the array because what you're really looking to do is to extract the phrase following the keyword. The example below shows how you can use a function to isolate the phrase.
Function ExtractByKeyword(ByVal source As String, _
ByVal keyword As String) As String
'--- extracts a phrase (substring) from the given source,
' beginning with the keyword and ending with the next
' (unknown) keyword.
' Keywords are delimited by a preceding space ' ' and
' followed by a colon ":" or EOL
Dim pos1 As Long
pos1 = InStr(1, source, keyword, vbTextCompare)
If pos1 = 0 Then
'--- the keyword was not found, so return a null string
ExtractByKeyword = vbNullString
Exit Function
End If
Dim phrase As String
'--- skip over the keyword and find the next keyword
' (i.e. look for the next colon)
Dim pos2 As Long
pos2 = InStr(pos1 + Len(keyword) + 1, source, ":", vbTextCompare)
If pos2 = 0 Then
'--- this is the last keyword and phrase in the source
phrase = Right$(source, Len(source) - pos1 - Len(keyword) - 1)
Else
'--- now work backwards from the second keyword to find the
' end of the phrase (which is the space just before the
' second keyword
Dim pos3 As Long
pos3 = InStrRev(source, " ", pos2, vbTextCompare)
Dim startsAt As Long
Dim phraseLen As Long
startsAt = pos1 + Len(keyword) + 2
phraseLen = pos3 - startsAt
phrase = Mid$(source, startsAt, phraseLen)
End If
ExtractByKeyword = phrase
End Function
I used the test routine below to check the extraction:
Option Explicit
Sub test()
Const medInfo As String = "Clinical: history of heart disease" & _
" Labs: elevated cholesterol on 8Aug" & _
" Meds: just started cholesterol medication" & _
" Supplements: none" & _
" Allergies: none" & _
" Activity: recently started going to YMCA 3x/wk (elliptical and some weight lifting"
Dim phrase As String
phrase = ExtractByKeyword(medInfo, "Labs")
If phrase <> vbNullString Then
Debug.Print " Labs -> '" & phrase & "'"
Else
Debug.Print "Keyword not found!"
End If
phrase = ExtractByKeyword(medInfo, "Clinical")
If phrase <> vbNullString Then
Debug.Print " Clinical -> '" & phrase & "'"
Else
Debug.Print "Keyword not found!"
End If
phrase = ExtractByKeyword(medInfo, "Activity")
If phrase <> vbNullString Then
Debug.Print " Activity -> '" & phrase & "'"
Else
Debug.Print "Keyword not found!"
End If
phrase = ExtractByKeyword(medInfo, "Meds")
If phrase <> vbNullString Then
Debug.Print " Meds -> '" & phrase & "'"
Else
Debug.Print "Keyword not found!"
End If
phrase = ExtractByKeyword(medInfo, "Allergies")
If phrase <> vbNullString Then
Debug.Print "Allergies -> '" & phrase & "'"
Else
Debug.Print "Keyword not found!"
End If
End Sub

Related

How to vary the length of a combination of bounds based on the length of an array

I have this script in VBA and I need to apply it to different paths that I need to split. Each path is different in length and has several slash delimiters (/) separating it.
If I exceed the number of elements in the array in the LBound function, an error is returned to me.
Question
How do I dynamically combine this pattern based on length.
Example with 3 elements
That is, if I have an initial array of this type
"category / subcategory / product"
I have to get
"category; category> subcategory; category> subcategory> product"
I have many paths of this type, but sometimes they are composed of 3 parts, other times 4, 5 or even more. This is my starting VBA.
Public Sub TestMe()
Dim strFolderString As String
Dim arrFolderString As Variant
Dim result As String
Dim lenght As Integer
strFolderString = "category\subcategory\product\CustomerName\ProductName\2017\"
arrFolderString = Split(strFolderString, "\")
lenght = UBound(arrFolderString)
result = _
arrFolderString(LBound(arrFolderString) + 1) & ";" & _
arrFolderString(LBound(arrFolderString) + 1) & " > " & _
arrFolderString(LBound(arrFolderString) + 2) & ";" & _
arrFolderString(LBound(arrFolderString) + 1) & " > " & _
arrFolderString(LBound(arrFolderString) + 2) & " > " & _
arrFolderString(LBound(arrFolderString) + 3)
Debug.Print result
End Sub
You may try the following approach to get the dynamic path infos by a tricky loop restricting the inner loop to the current outer value:
Sub PathInfo()
'1) define path input
Dim strFolderString As String
strFolderString = _
"category\subcategory\product\CustomerName\ProductName\2017\"
'remove end slash
strFolderString = Replace(strFolderString & "\", "\\", "")
'2) split parts into array
Dim arrFolderString As Variant
arrFolderString = Split(strFolderString, "\")
'3) Provide for sufficient elements in results array
Dim lastIndex As Long: lastIndex = UBound(arrFolderString)
Dim results(): ReDim results(0 To lastIndex)
'4) Join Path infos
Dim i As Long, j As Long
For i = 0 To lastIndex
Dim delim As String: delim = ""
For j = 0 To i ' << restrict inner loop to i :-)
Dim tmp As String
results(i) = tmp & delim & arrFolderString(j)
delim = ">"
Next j
tmp = results(i)
Next
'5) Show Result
Debug.Print Join(results, ";" & vbNewLine)
End Sub
Results in VB Editors immediate window
category;
category>subcategory;
category>subcategory>product;
category>subcategory>product>CustomerName;
category>subcategory>product>CustomerName>ProductName;
category>subcategory>product>CustomerName>ProductName>2017

Best way to interpret HTML Response and paste on Worksheet

I have a URL that returns a ton of information that I need to break apart into rows/columns etc.
So far I have been able to get the .responsetext and then use Split to break it down, but I'm wondering best approach for getting this data onto spreadsheet as I'm about to do more "Split" and I feel like there is a better way using perhaps Arrays?
Macro:
Sub TEstHTML()
Dim URLStr As String
URLStr = "PrivateURL"
'< VBE > Tools > References > Microsoft Scripting Runtime & Microsoft XML, V6.0
Dim xhr As MSXML2.XMLHTTP60
Dim table As MSHTML.HTMLTable
Dim tableCells As MSHTML.IHTMLElementCollection
Set xhr = New MSXML2.XMLHTTP60
With xhr
.Open "GET", URLStr, False
.send
If .readyState = 4 And .status = 200 Then
Set doc = New MSHTML.HTMLDocument
doc.body.innerHTML = .responseText
Debug.Print doc.body.innerHTML
Stop
Else
Debug.Print "Error" & vbNewLine & "Ready state: " & .readyState & vbNewLine & "HTTP request status: " & .status
End If
End With
Dim SplitArr() As String
SplitArr = Split(doc.body.innerHTML, "{")
Debug.Print SplitArr(1)
Stop
End Sub
The page sends back a lot of data formatted like so:
{"ClientCode":"CLICODE","ClientName":"MyClient","ContractNumber":"2021-1",...}
Which the Split function returns:
"ClientCode":"CLICODE","ClientName":"MyClient","ContractNumber":"2021-1",...
I need to turn this into Colum Headers ClientCode & ClientName & ContractNumber and then paste the values one SplitArr(i) at a time. Note there are many column headers I'd like this to not be hardcoded ideally, but if needed I can make the column headers and then paste information somehow.
Update:
I'm not sure if I'm doing it wrong, or this data is/isn't JSON but this tool works great. I did have to make a function to "clean" the strings though. Here is what I ended up with..
Sub Testing()
Dim URLStr As String
URLStr = "URL"
Dim HTMLDoc As MSHTML.HTMLDocument
Set HTMLDoc = New MSHTML.HTMLDocument
Set HTMLDoc = Get_HTMLDocument(URLStr)
Dim HTMLDocStr As String
HTMLDocStr = HTMLDoc.body.innerHTML
HTMLDocStr = ConvertToJsonClear(HTMLDocStr)
Dim SplitArr() As String, Parsed As Dictionary, k, l As Long
SplitArr = Split(HTMLDocStr, "{")
For X = 1 To UBound(SplitArr) Step 1
l = 0
HTMLDocStr = ConvertToJsonClear(SplitArr(X))
Set Parsed = JsonConverter.ParseJson(HTMLDocStr)
For Each k In Parsed.Keys
l = l + 1
If X = 1 Then
Cells(1, l).Value = k
End If
Cells(X + 1, l).Value = Parsed(k)
'Debug.Print k & " = "; Parsed(k)
Next
'Stop
Next X
Stop
End Sub
Public Function ConvertToJsonClear(JSonStr As String) As String
JSonStr = JsonConverter.ConvertToJson(JSonStr)
JSonStr = Replace(JSonStr, "[", "")
JSonStr = Replace(JSonStr, "]", "")
JSonStr = Replace(JSonStr, "\", "")
If Left(JSonStr, 1) = Chr(34) Then
'Stop
JSonStr = Right(JSonStr, Len(JSonStr) - 1)
End If
If Left(JSonStr, 1) <> "{" Then
'Stop
JSonStr = "{" & JSonStr
End If
If Right(JSonStr, 3) = "},""" Then
'Stop
'Debug.Print Right(JSonStr, 3)
'Stop
JSonStr = Left(JSonStr, Len(JSonStr) - 2) & Chr(34)
End If
If Right(JSonStr, 1) = "," Then
'Stop
JSonStr = Left(JSonStr, Len(JSonStr) - 1)
End If
ConvertToJsonClear = JSonStr
'Debug.Print ConvertToJsonClear
End Function
I don't have my real data in front of me, but I tackled this a home with a homemade TestStr. The VBA-JSON parser linked in OP Comments by #TimWilliams worked great with a bit of string manipulation. I'll have to play around with real data and perhaps clean it up, but this works for now!
Public Sub JsonTest()
Dim TestStr As String, SplitArr() As String, k, I As Long
TestStr = "{""CC"":""TestA"",""DD"":""RESA"",""ZZ"":""RESAA""},{""CC"":""TestB"",""DD"":""RESB"",""ZZ"":""RESBB""}"
SplitArr = Split(TestStr, "{")
For I = 1 To UBound(SplitArr) Step 1
TestStr = JsonConverter.ConvertToJson("{" & SplitArr(I))
TestStr = Left(TestStr, Len(TestStr) - 1)
TestStr = Right(TestStr, Len(TestStr) - 1)
TestStr = Replace(TestStr, "\", "")
'Debug.Print TestStr
'Stop
Set Parsed = JsonConverter.ParseJson(TestStr)
For Each k In Parsed.Keys
Debug.Print k & " = " & Parsed(k)
'Stop
Next
Next
End Sub

Remove delimiters from Join() Function in EXcel VBA

I am a novice programmer and I'm building a form via VBA for excel where the user will input employee's time sheet and their initials via 16 text box's in the form. The text boxes data are stored to a string array. The code is:
Dim initials(15) As String
initials(0) = TB_Initials_1
initials(1) = TB_Initials_2
initials(2) = TB_Initials_3
...
initials(15) = TB_Initials_15
After using the find function and referencing some data from a one excel sheet, I use
ActiveCell.Offset(0, 2).Value = Join(initials, ".")
to output the following
"js.rs.............." to the active cell in a different excel sheet, (I only entered 2 of the 16 input boxes, hence there's two initials. JS.RS
The trailing .............. is what I want to remove. this will be imported into a Database later via the excel sheet.
How can I remove the xtras ".........'s at the end of the string? I have tried the "Trim()" function, but that does not work in my case. Everything i've tried online does not seem to work either or is referencing items from a work book, not a text box.
Any help is appreciated.
The entire code is below:
Option Explicit
'Variable declaration
Dim startTime(15), endTime(15), ST_Finish_Date As Date
Dim totalmin(15), Total_min, Total_Cost, Rate(15), Line_cost(15), Cost_Per_Part As String
Dim initials(15) As String
Dim i, ii As Integer
Dim Found_ini(15) As Range
Dim Found As Range 'returned value from find
Dim TBtraveller_value As String 'text box traveller value
Dim Found2 As Range 'store part code range
Dim TBDESC As Range ' Returned value from 2nd search
Dim BL_Find_Check As Boolean
Private Sub CB_Write_Click()
create_csv
End Sub
Private Sub Close_Form_Click()
Unload Traveller_Entry
End Sub
'still need to make this for every start / stop time text box.
Private Sub TB_Time_Start_1_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
Dim myvar As String
If Not Me.TB_Time_Start_1 Like "??:??" Then
MsgBox "Please use format 'HH:MM'"
Cancel = True
Exit Sub
End If
myvar = Format(Me.TB_Time_Start_1, "hh:mm")
Me.TB_Time_Start_1 = myvar
End Sub
Public Sub travellerNUM_TextBox_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Workbooks("Traveller entryxlsm.xlsm").Activate
TBtraveller_value = travellerNUM_TextBox.Value
If TBtraveller_value = "" Then
MsgBox ("Enter a Shop Traveller Number!")
Exit Sub
Else
TBtraveller_value = travellerNUM_TextBox.Value
Set Found = Sheets("woss").Range("A:A").Find(what:=TBtraveller_value, lookat:=xlWhole)
If Found Is Nothing Then
MsgBox (TBtraveller_value & " Not Found!")
Exit Sub
Else
Part_Code_BOX.Value = Found.Offset(0, 1) 'enters the info into the Part Code Box.
Set Found2 = Found.Offset(0, 1)
End If
If Part_Code_BOX = "" Then
MsgBox ("Traveller number " & TBtraveller_value & " has no part code associated with it." & vbCrLf & "Check Work Order Spread Sheet is FULLY Complete.")
BL_Find_Check = True
Exit Sub
End If
Set TBDESC = Sheets("ProductList").Range("B:B").Find(what:=Found2, lookat:=xlPart)
If TBDESC Is Nothing Then
MsgBox (" Dscription Not Found!")
Else
Desc_Box = TBDESC.Offset(0, 1) 'enters the description into the description Box.
FinishDate_Box = Found.Offset(0, 8) 'enters the finish date into the finish date Box.
Employee = Found.Offset(0, 2) 'enters the Employee name into the employee name Box.
End If
End If
End Sub
Public Sub CB_POST_Click()
On Error Resume Next
startTime(0) = TB_Time_Start_1.Value
startTime(1) = TB_Time_Start_2.Value
startTime(2) = TB_Time_Start_3.Value
startTime(3) = TB_Time_Start_4.Value
startTime(4) = TB_Time_Start_5.Value
startTime(5) = TB_Time_Start_6.Value
startTime(6) = TB_Time_Start_7.Value
startTime(7) = TB_Time_Start_8.Value
startTime(8) = TB_Time_Start_9.Value
startTime(9) = TB_Time_Start_10.Value
startTime(10) = TB_Time_Start_11.Value
startTime(11) = TB_Time_Start_12.Value
startTime(12) = TB_Time_Start_13.Value
startTime(13) = TB_Time_Start_14.Value
startTime(14) = TB_Time_Start_15.Value
startTime(15) = TB_Time_Start_16.Value
endTime(0) = TB_Time_Stop_1.Value
endTime(1) = TB_Time_Stop_2.Value
endTime(2) = TB_Time_Stop_3.Value
endTime(3) = TB_Time_Stop_4.Value
endTime(4) = TB_Time_Stop_5.Value
endTime(5) = TB_Time_Stop_6.Value
endTime(6) = TB_Time_Stop_7.Value
endTime(7) = TB_Time_Stop_8.Value
endTime(8) = TB_Time_Stop_9.Value
endTime(9) = TB_Time_Stop_10.Value
endTime(10) = TB_Time_Stop_11.Value
endTime(11) = TB_Time_Stop_12.Value
endTime(12) = TB_Time_Stop_13.Value
endTime(13) = TB_Time_Stop_14.Value
endTime(14) = TB_Time_Stop_15.Value
endTime(15) = TB_Time_Stop_16.Value
initials(0) = TB_Initials_1
initials(1) = TB_Initials_2
initials(2) = TB_Initials_3
initials(3) = TB_Initials_4
initials(4) = TB_Initials_5
initials(5) = TB_Initials_6
initials(6) = TB_Initials_7
initials(7) = TB_Initials_8
initials(8) = TB_Initials_9
initials(9) = TB_Initials_10
initials(10) = TB_Initials_11
initials(11) = TB_Initials_12
initials(12) = TB_Initials_13
initials(13) = TB_Initials_14
initials(14) = TB_Initials_15
initials(15) = TB_Initials_16
For i = LBound(initials) To UBound(initials)
Set Found_ini(i) = Sheets("rate").Range("B:B").Find(what:=initials(i), lookat:=xlWhole)
Rate(i) = Found_ini(i).Offset(0, 1) 'finds rate for given initials
totalmin(i) = DateDiff("N", startTime(i), endTime(i))
If Found_ini(i) Is Nothing Then
MsgBox (initials(i) & " Not Found! Update Employee Database.")
Exit Sub
'If IsEmpty(Found_ini(i)) = False And IsEmpty(startTime(i)) = True And IsEmpty(endTime(i)) = True Then
'MsgBox "Enter Some Initials, None Found"
Exit Sub
End If
Next
For ii = LBound(totalmin) To UBound(totalmin)
Line_cost(ii) = totalmin(ii) / 60 * Rate(ii)
Next
Total_min = Application.WorksheetFunction.Sum(totalmin)
Total_Cost = Application.WorksheetFunction.Sum(Line_cost)
Cost_Per_Part = Total_Cost / TextBOX_QTYBUILT
If Total_min = 0 Then
MsgBox (" Enter Some Time!")
ElseIf Total_min < 0 Then
MsgBox ("Time is NEGATIVE. Check Entered Times.")
End If
If BL_Find_Check = False Then
MsgBox "The number of minutes between two Times : " & Total_min & vbNewLine & "total cost: " & Total_Cost _
& vbNewLine & "cost Per Part " & Cost_Per_Part, vbInformation, "Minutes Between Two Times"
Sheets("test").Select
Range("A1048576").Select
ActiveCell.End(xlUp).Select
ActiveCell.Offset(1, 0).Select
ActiveCell.Offset(0, 0).Value = FinishDate_Box 'Traveller finish Date
ActiveCell.Offset(0, 1).Value = TBtraveller_value 'Traveller Number
ActiveCell.Offset(0, 2).Value = Join(initials, ".") 'Traveller Employee Given to
ActiveCell.Offset(0, 3).Value = Part_Code_BOX.Value ' part number
ActiveCell.Offset(0, 4).Value = Total_Cost ' traveller total cost
ActiveCell.Offset(0, 5).Value = Cost_Per_Part 'Traveller cost per part
End If
End Sub
Sub create_csv()
Dim FileName As String
Dim PathName As String
Dim ws As Worksheet
Set ws = ActiveWorkbook.Sheets("test")
FileName = "CSV_Output_R1.csv"
PathName = Application.ActiveWorkbook.Path
ws.Copy
ActiveWorkbook.SaveAs FileName:=PathName & "\" & FileName, _
FileFormat:=xlCSV, CreateBackup:=False
End Sub
Thank you,
You can use WorksheetFunction.TextJoin() in Excel2019+ in one string:
ActiveCell.Offset(0, 2).Value = WorksheetFunction.TextJoin(".", True, initials)
A small example for comparison:
Sub test1()
Dim arr(1 To 15)
For i = 1 To 15
arr(i) = IIf(Rnd() > 0.7, "TXT", "")
Next
Debug.Print "With Join(): " & Join(arr, ".")
Debug.Print "With TextJoin(): " & WorksheetFunction.TextJoin(".", True, arr)
End Sub
Output
With Join(): ..TXT........TXT..TXT..
With TextJoin(): TXT.TXT.TXT
Here is a function that I just made to trim empty elements off the end of your array:
Function TrimArray(ByRef StringArray() As String) As String()
'This function removes trailing empty elements from arrays
'Searching from the last element backwards until a non-blank is found
Dim i As Long
For i = UBound(StringArray) To LBound(StringArray) Step -1
If StringArray(i) <> "" Then Exit For
Next i
If i < LBound(StringArray) Then i = LBound(StringArray)
'Creating an array with the correct size to hold the non-blank elements
Dim OutArr() As String
OutArr = StringArray
ReDim Preserve OutArr(LBound(StringArray) To i)
TrimArray = OutArr
End Function
You would use it like so:
Dim Output() As String
Output = TrimArray(initials)
MsgBox Join(Output, ".") & "."
You could build it like this instead of using Join():
ActiveCell.Offset(0, 2).Value = initials(0)
For Counter = 1 To 15
If initials(Counter) <> "" Then
ActiveCell.Offset(0, 2).Value = ActiveCell.Offset(0, 2).Value + "." + initials(Counter)
End If
Next Counter

Best way to extract content in Word document to SQL database?

I have a question bank of 100,000 questions in the form of word documents. The questions are in text with some containing images. Is there any efficient way of extracting the questions one at a time (including images) and importing them into an SQL database? Would prefer not to have to convert the text to images as the questions may have to be edited. Thanks!
here is a beginning
separates question number, question text, each answer number and each answer text
then it prints them in immediate window
please try it with one of your documents
Option Explicit
Sub parse()
Dim rgx As Object
Set rgx = CreateObject("vbscript.regexp")
rgx.MultiLine = True
rgx.Global = True
rgx.pattern = "^[\s]+|[\s]+$"
Dim s As String
Dim i As Integer
Dim qNum As Long
Dim qest As String
Dim aNum As Integer
Dim answ As String
Dim par As Paragraphs
Set par = ActiveDocument.Paragraphs
Dim p As Integer
For p = 1 To par.Count
s = rgx.Replace(par(p).Range.Text, "") ' trim whitespace (leading and trailing)
' Debug.Print "--- "; s
Select Case Left(s, 1)
Case "0" To "9" ' question found
qNum = CDec(Split(s, ".")(0)) ' number
i = 1
qest = rgx.Replace(Split(s, ".", 2)(1), "") ' first line of text
Do While True
s = rgx.Replace(par(p + i).Range.Text, "") ' check for multiline question
If Len(s) > 0 Then
If Left(s, 1) = "(" Then
p = p + i - 1 ' it is an answer line, so exit
Exit Do
Else
qest = qest & vbNewLine & s ' assemble multiline question
End If
End If
i = i + 1
Loop
Debug.Print vbNewLine; "question # "; qNum; vbTab; qest
Case "(" ' answer found
aNum = CDec(Mid(s, 2, 1)) ' number
answ = Split(s, ")", 2)(1) ' text
Debug.Print "answer # "; aNum, answ
End Select
Next p
End Sub

VBA - How to build an array with multiple delimiters of varying sizes?

How can I build an array if I have multiple delimiters, where some delimiters are single character and others are multiple characters?
Sub Example()
Dim exString As String
Dim myString() As String
exString = "A,B C;D > E"
myString() = Split(exString, "," & " " & ";" & " > ")
End Sub
The result I want in my array is:
myString(0) is A
myString(1) is B
myString(2) is C
myString(3) is D
myString(4) is E
But using Split() in this way doesn't work. I do know that I can use Replace() to replace every single delimiter with a common one, but I have a lot of different delimiters and variations of multiple character delimiters. Using Replace() isn't desirable to me. What can I do?
You can have lots of problems in VBA as well:
'Add a reference to Microsoft VBScript Regular Expressions 5.5 (Tools -> References...)
Dim exString As String
exString = "A,B C;D > E"
Dim re As New RegExp
re.Pattern = "(,| |;|>)+"
re.Global = True
Dim myString() As String
myString = Split(re.Replace("A,B C;D > E", ","), ",")
Setting re.Pattern defines what to look for. | represents finding A or B, so the regular expression will match on , or or ; or >.
Multiple instances should be treated as one (e.g. between the D and the E there are three characters, but there should be only one split), so add a + at the end (and wrap everything else in ()).
Replace then replaces any of the matched patterns with , and gives back a string like this:
A,B,C,D,E
on which we can simply call Split to get back the array.
Reference: VBScript Regular Expressions
Instead of using regular expressions to match the delimiter characters, you could use regexes to match the non-delimiter characters:
Dim re As New RegExp
re.Pattern = "[^, ;>]+" 'The ^ unmatches any characters within the []
re.Global = True
Dim match As Match
For Each match In re.Execute(exString)
'do something with each result here
Debug.Print match.Value
Next
This is sufficient if all you need is to iterate over the results and do something with them. If you specifically need an array with the results:
Dim re As New RegExp
re.Pattern = "[^, ;>]+"
re.Global = True
Dim matches As MatchCollection
Set matches = re.Execute(exString)
ReDim myString(matches.Count) As String
Dim i As Integer
For i = 0 To matches.Count - 1
myString(i) = matches(i).Value
Next
You were on the right track with your function. Using a ParamArray you can easily change the number and position of your delimiters.
Code
Function MultiSplit(SourceText As String, ParamArray Delimiters()) As String()
Dim v As Variant
For Each v In Delimiters
SourceText = Replace(SourceText, v, "•")
Next
MultiSplit = Split(SourceText, "•")
End Function
Test
Sub Test()
Const example As String = "A,B C;D > E"
Dim a1, a2, a3, Incorrect
Incorrect = MultiSplit(example, " ", " > ")
a1 = MultiSplit(example, " > ", ",", ";", " ")
a2 = MultiSplit(example, " > ", ",")
a3 = MultiSplit(example, " > ")
End Sub
Result
NOTE: When using multi-character delimiters, the order that the delimiters are processed matters. Notice that A1 is split proper but Incorrect is not split as intended because the space delimiter came before " > ".
In this situation, I found the following function to be perfect for my needs:
Function MultiSplit(SourceText As String, Optional SingleCharDelimiter As String, Optional MultiCharDelimiter As String, _
Optional Separator As String) As String()
'Created by Tyeler for use by all.
'SourceText is your input string.
'SingleCharDelimiter is a string of desired delimiters.
'SingleCharDelimiter format is a string fully concatenated with no character separation.
' (ex. "-.;:, " MultiSplit will use those 6 characters as delimiters)
'SingleCharDelimiter's will remove blanks from the array in the event two single delimiters
' are next to each other.
'MultiCharDelimiter is a string of specific multi-character delimiters.
'MultiCharDelimiters can be separated by the optional Separator
'Separator is an optional value used to separate multiple MultiCharDelimiters.
' (ex. MultiCharDelimiter = "A A,B B,C C" // Separator = "," // This will make the function
' delimit a string by "A A", "B B", and "C C")
'MultiSplit will make an array based on any delimiter (Including delimiters with
' multiple characters).
If MultiCharDelimiter = "" And SingleCharDelimiter = "" Then Exit Function
Dim i As Integer, n As Integer, dlimit
Dim delColl As New Collection
Dim newString As String: newString = SourceText
Dim delArr() As String, strgArr() As String, delFull() As String
Dim delSep As String, a As Integer: a = 33
Do While InStr(SingleCharDelimiter, Chr(a)) <> 0 Or InStr(MultiCharDelimiter, Chr(a)) <> 0 _
Or InStr(Separator, Chr(a)) <> 0 Or InStr(SourceString, Chr(a)) <> 0 'Find intermediate delimiter
a = a + 1
Loop
delSep = Chr(a)
If MultiCharDelimiter <> "" Then
If Separator <> "" Then 'If there's no delimiter for the delimiter array, assume MultiCharDelimiter is the delimiter
delArr() = Split(MultiCharDelimiter, Separator)
For i = 0 To UBound(delArr)
If InStr(newString, delArr(i)) <> 0 Then newString = Replace(newString, delArr(i), delSep)
Next i
Else
newString = Replace(newString, MultiCharDelimiter, delSep)
End If
End If
Erase delArr
For i = 1 To Len(SingleCharDelimiter) 'Build a collection of user defined delimiters
delColl.Add Mid(SingleCharDelimiter, i, 1)
Next i
For Each dlimit In delColl 'Replace all delimiters in the string with a single common one
newString = Replace(newString, dlimit, delSep)
Next dlimit
strgArr() = Split(newString, delSep)
ReDim delFull(LBound(strgArr) To UBound(strgArr))
n = LBound(strgArr)
For i = LBound(strgArr) To UBound(strgArr) 'Get rid of empty array items
If strgArr(i) <> "" Then
delFull(n) = strgArr(i)
n = n + 1
End If
Next i
n = n - 1
ReDim Preserve delFull(LBound(strgArr) To n)
MultiSplit = delFull 'Send the delimited array
Erase delFull
Erase strgArr
End Function
This function will return an array of values that were separated by user defined delimiters.
To use this function, simply call on it and supply your full string and desired delimiters:
Sub Example1()
Dim exString As String
Dim myString() As String
Dim c, n
exString = "A,B C;D > E"
myString() = MultiSplit(exString, ", ;", " > ")
n = 0
For Each c In myString
Debug.Print "(" & n & ") = " & c
n = n + 1
Next c
End Sub
This will yield the desired result where the array is filled with only ABCDE.
A more complicated example:
Sub Example2()
Dim myString As String, c, n
n = 0
myString = "The,Quickupside-downBrownjelloFox_Jumped[Over] ThegiantLazyjelloDog"
For Each c In MultiSplit(myString, ",_[] ", "upside-down,jello,giant", ",")
Debug.Print "(" & n & ") = " & c
n = n + 1
Next c
End Sub
This will yield the following:
The following is a built-upon version of the code that Thomas Inzina graciously provided.
The following limitations have been removed:
The order that the delimiters are listed in the function.
The temporary delimiter being a set specific character.
The option to include or remove empty array items.
The function changing the reference (ByRef vs ByVal)
Passing an array of delimiters vs listing individual delimiters
Function MultiSplitX(ByVal SourceText As String, RemoveBlankItems As Boolean, ParamArray Delimiters()) As String()
Dim a As Integer, b As Integer, n As Integer
Dim i As Integer: i = 251
Dim u As Variant, v As Variant
Dim tempArr() As String, finalArr() As String, fDelimiters() As String
If InStr(TypeName(Delimiters(0)), "()") <> 0 And LBound(Delimiters) = UBound(Delimiters) Then
ReDim fDelimiters(LBound(Delimiters(0)) To UBound(Delimiters(0))) 'If passing array vs array items then
For a = LBound(Delimiters(0)) To UBound(Delimiters(0)) 'build that array
fDelimiters(a) = Delimiters(0)(a)
Next a
Else
fDelimiters = Delimiters(0)
End If
Do While InStr(SourceText, Chr(i)) <> 0 And i < 251 'Find an unused character
i = i + 1
Loop
If i = 251 Then 'If no unused character in SourceText, use single character delimiter from supplied
For a = LBound(fDelimiters) To UBound(fDelimiters)
If Len(fDelimiters(a)) = 1 Then i = Asc(fDelimiters(a))
Next a
End If
If i = 251 Then 'If no single character delimiters can be used, error.
MsgBox "SourceText uses all character type." & vbCrLf & "Cannot split SourceText into an array.", _
vbCritical, "MultiSplitX Run-Time Error"
Exit Function
End If
Debug.Print i
For a = LBound(fDelimiters) To UBound(fDelimiters) 'Sort Delimiters by length
For b = a + 1 To UBound(fDelimiters)
If Len(fDelimiters(a)) < Len(fDelimiters(b)) Then
u = fDelimiters(b)
fDelimiters(b) = fDelimiters(a)
fDelimiters(a) = u
End If
Next b
Next a
For Each v In fDelimiters 'Replace Delimiters with a common character
SourceText = Replace(SourceText, v, Chr(i))
Next
tempArr() = Split(SourceText, Chr(i)) 'Remove empty array items
If RemoveBlankItems = True Then
ReDim finalArr(LBound(tempArr) To UBound(tempArr))
n = LBound(tempArr)
For i = LBound(tempArr) To UBound(tempArr)
If tempArr(i) <> "" Then
finalArr(n) = tempArr(i)
n = n + 1
End If
Next i
n = n - 1
ReDim Preserve finalArr(LBound(tempArr) To n)
MultiSplitX = finalArr
Else: MultiSplitX = tempArr
End If
End Function
Use of this function doesn't change from how Thomas had it, with the exception that there's an added boolean statement.
Example 1
In this example, RemoveBlankItems has been set to True.
Sub Example1()
Dim myString As String, c, n
n = 0
myString = "The,Quickupside-downBrownjelloFox_Jumped[Over] ThegiantLazyjelloDog"
For Each c In MultiSplitX(myString, True, ",", "-", "upside-down", "jello", " ", "[", "]", "giant", "_")
Debug.Print "(" & n & ") = " & c
n = n + 1
Next c
End Sub
This results in the following output:
Example 2
In this example we have RemoveBlankItems set to False.
Sub Example2()
Dim myString As String, c, n
n = 0
myString = "The,Quickupside-downBrownjelloFox_Jumped[Over] ThegiantLazyjelloDog"
For Each c In MultiSplitX(myString, True, ",", "-", "upside-down", "jello", " ", "[", "]", "giant", "_")
Debug.Print "(" & n & ") = " & c
n = n + 1
Next c
Debug.Print myString
End Sub
This results in the following output:
Example 3
In this example, instead of listing our delimiters in the function, we have them typed out in a string and insert an array in the function instead:
Sub Example3()
Dim myString As String, c, n
Dim myDelimiters As String
n = 0
myString = "The,Quickupside-downBrownjelloFox_Jumped[Over] ThegiantLazyjelloDog"
myDelimiters = ",|-|upside-down|jello| |[|]|giant|_"
For Each c In MultiSplitX(myString, True, Split(myDelimiters, "|"))
Debug.Print "(" & n & ") = " & c
n = n + 1
Next c
Debug.Print myString
End Sub
This has the same result as if they had been listed individually:
The Reason RemoveBlankItems Is Desirable
There are some instances in which you DON'T want to have blanks in your array. An example of this would be if you're using your array as a bank of search words that are cycling through a range on a spread sheet. Another example would be if you're manipulating strings of text based on values in the array.
There are also times when you would want to retain the blanks in the array. As Thomas described, in the event you're using this on a CSV file, where maintaining the spaces as columns is desired. Or you're using it to break apart, for example, HTML coding and wish to retain the line format.
Perhaps:
Sub Example()
Dim exString As String
Dim myString() As String
exString = "A,B C;D > E"
exString = Replace(exString, ",", " ")
exString = Replace(exString, ";", " ")
exString = Replace(exString, ">", " ")
exString = Application.WorksheetFunction.Trim(exString)
myString() = Split(exString, " ")
msg = ""
For Each a In myString
msg = msg & vbCrLf & a
Next a
MsgBox msg
End Sub

Resources