Extracting Text from PDF - Excel VBA - excel

I have code that was written for me and it works perfect for four of the entries I am wanting to find, but is there a way that I can add another field value to search for, like ID? I have tried to change the code for what I think would work, but I got errors. I have never coded something this complex, so I do not understand fully what the author did in each section.
Option Explicit
Public Sub ExtractFieldValues()
Const CONSTLAST As Long = 1
Const CONSTFIRST As Long = 2
Const CONSTMIDDLE As Long = 3
Const CONSTRANK As Long = 4
Const TABLEONE As String = "Table 1"
Const FIELDVALUES As String = "FieldValues"
Const LAST_FIRST_MIDDLE As String = "last first middle"
Const FIELDNAMES As String = LAST_FIRST_MIDDLE & " rank"
Const NUMRECORDS As Long = 5 '6
Dim ƒ As Excel.WorksheetFunction: Set ƒ = Excel.WorksheetFunction
Dim ¡ As Long
Dim lrd As Long
Dim nextRowOutput As Long
Dim arngFoundCells(CONSTLAST To CONSTRANK) As Range
Dim varFoundCell As Variant
Dim lngFirstFoundRow As Long
Dim lngNextFoundRow As Long
Dim rngNextFindStart As Range
Dim dictFields As Object
Dim astrFieldNames() As String
Dim astrSplitValues() As String
Dim strFoundValue As String
Dim lngFieldCount As Long
Set dictFields = CreateObject("Scripting.Dictionary")
dictFields.CompareMode = vbTextCompare
With Worksheets
On Error Resume Next
.Add(After:=.Item(.count)).name = FIELDVALUES
On Error GoTo 0
Application.DisplayAlerts = False
If .Item(.count).name <> FIELDVALUES Then
.Item(.count).Delete
.Item(FIELDVALUES).UsedRange.Clear
End If
Application.DisplayAlerts = True
.Item(TABLEONE).Activate
End With
astrFieldNames = Split(" " & FIELDNAMES, " ") ' Force index zero to a blank -> treat as base 1
Set dictFields = CreateObject("Scripting.Dictionary")
For ¡ = CONSTLAST To CONSTRANK
dictFields.Add astrFieldNames(¡), ""
Next ¡
lrd _
= Cells _
.find _
( _
What:="*" _
, After:=Cells(1) _
, LookIn:=xlFormulas _
, Lookat:=xlPart _
, SearchOrder:=xlByRows _
, SearchDirection:=xlPrevious _
) _
.Row
With Range(Rows(1), Rows(lrd))
For ¡ = CONSTLAST To CONSTRANK
Set arngFoundCells(¡) = .find(What:=astrFieldNames(¡), After:=Cells(1))
Next ¡
lngFirstFoundRow _
= ƒ.Min _
( _
arngFoundCells(CONSTLAST).Row _
, arngFoundCells(CONSTFIRST).Row _
, arngFoundCells(CONSTMIDDLE).Row _
)
nextRowOutput = 1
Do
For ¡ = CONSTLAST To CONSTRANK
' Debug.Print arngFoundCells(¡).Address; " ";
dictFields.Item(astrFieldNames(¡)) = ""
Next ¡
' Debug.Print
Select Case True
Case arngFoundCells(CONSTFIRST).Row = arngFoundCells(CONSTMIDDLE).Row:
If arngFoundCells(CONSTRANK).Row <> arngFoundCells(CONSTFIRST).Row Then
Set arngFoundCells(CONSTRANK) = arngFoundCells(CONSTFIRST)
End If
For Each varFoundCell In arngFoundCells
strFoundValue = ƒ.Trim(Replace(varFoundCell.Value2, vbLf, " ")) & " "
If strFoundValue Like "[']*" Then strFoundValue = Mid$(strFoundValue, 2)
If LCase$(strFoundValue) Like astrFieldNames(CONSTLAST) & " " Then
strFoundValue = ƒ.Trim(strFoundValue & Rows(varFoundCell.Row + 1).Cells(1).Value2) & " "
End If
If LCase$(strFoundValue) Like LAST_FIRST_MIDDLE & "*" _
And Len(strFoundValue) - Len(Replace(strFoundValue, " ", "")) < 5 _
Then
strFoundValue = ƒ.Trim(strFoundValue & Rows(varFoundCell.Row + 1).Cells(1).Value2) & " "
End If
astrSplitValues = Split(" " & strFoundValue, " ") ' Force index zero to a blank -> treat as base 1
lngFieldCount = Int(UBound(astrSplitValues) / 2)
For ¡ = 1 To lngFieldCount
dictFields.Item(LCase(astrSplitValues(¡))) = astrSplitValues(¡ + lngFieldCount)
Next ¡
Next varFoundCell
Case Else
Debug.Print " SKIPPED: ";
For ¡ = CONSTLAST To CONSTRANK
Debug.Print arngFoundCells(¡).Address; " ";
Next ¡
Debug.Print
For ¡ = CONSTLAST To CONSTRANK
Debug.Print " "; ƒ.Trim(arngFoundCells(¡).Value2)
Next ¡
Debug.Print
End Select
Sheets(FIELDVALUES).Columns(1).Cells(nextRowOutput).Resize(4).Value _
= ƒ.Transpose(dictFields.Items)
nextRowOutput = nextRowOutput + NUMRECORDS
Set rngNextFindStart = Rows(arngFoundCells(CONSTFIRST).Row + 2).Cells(1)
For ¡ = CONSTLAST To CONSTRANK
Set arngFoundCells(¡) = .find(What:=astrFieldNames(¡), After:=rngNextFindStart)
Next ¡
lngNextFoundRow _
= ƒ.Min _
( _
arngFoundCells(CONSTLAST).Row _
, arngFoundCells(CONSTFIRST).Row _
, arngFoundCells(CONSTMIDDLE).Row _
)
Loop While lngNextFoundRow <> lngFirstFoundRow
End With
End Sub

Related

Any way I can speed up this sub procedure?

I figure there must be a way to drastically shorten this sub procedure. The reason why I even want to is because it's freezing up the application on the elseif userform1.optTerm line because that worksheet it pulls the data from is 6x longer in rows than the optInSeat list.
Sub LoadEmployee_Cmb_HC()
Dim isWS As Worksheet: Set isWS = ThisWorkbook.Worksheets("In Seat")
Dim tWs As Worksheet: Set tWs = ThisWorkbook.Worksheets("Terms")
Dim a, b As Long, c As Variant, i As Long
If UserForm1.optInSeat = True Then
If UserForm1.optEmployeeName = True Then
For i = 2 To isWS.Cells(Rows.Count, 4).End(xlUp).row
x = Application.CountIf(isWS.Range("D" & 4, "D" & i), _
isWS.Cells(i, 4).Value)
If x = 1 Then
UserForm1.cmbEmployee.AddItem isWS.Cells(i, 4) & " - " & isWS.Cells(i, 1)
End If
Next i
For a = 0 To UserForm1.cmbEmployee.ListCount - 1
For b = 0 To UserForm1.cmbEmployee.ListCount - 1
If UserForm1.cmbEmployee.List(b) > UserForm1.cmbEmployee.List(a) Then
c = UserForm1.cmbEmployee.List(a)
UserForm1.cmbEmployee.List(a) = UserForm1.cmbEmployee.List(b)
UserForm1.cmbEmployee.List(b) = c
End If
Next
Next
ElseIf UserForm1.optEmployeeID = True Then
For i = 2 To isWS.Cells(Rows.Count, 1).End(xlUp).row
x = Application.CountIf(isWS.Range("A" & 1, "A" & i), _
isWS.Cells(i, 1).Value)
If x = 1 Then
UserForm1.cmbEmployee.AddItem isWS.Cells(i, 1) & " - " & isWS.Cells(i, 4)
End If
Next i
For a = 0 To UserForm1.cmbEmployee.ListCount - 1
For b = 0 To UserForm1.cmbEmployee.ListCount - 1
If UserForm1.cmbEmployee.List(b) > UserForm1.cmbEmployee.List(a) Then
c = UserForm1.cmbEmployee.List(a)
UserForm1.cmbEmployee.List(a) = UserForm1.cmbEmployee.List(b)
UserForm1.cmbEmployee.List(b) = c
End If
Next
Next
End If
ElseIf UserForm1.optTerm = True Then
If UserForm1.optEmployeeName = True Then
For i = 2 To tWs.Cells(Rows.Count, 4).End(xlUp).row
x = Application.CountIf(tWs.Range("D" & 4, "D" & i), _
tWs.Cells(i, 4).Value)
If x = 1 Then
UserForm1.cmbEmployee.AddItem tWs.Cells(i, 4) & " - " & tWs.Cells(i, 1)
End If
Next i
For a = 0 To UserForm1.cmbEmployee.ListCount - 1
For b = 0 To UserForm1.cmbEmployee.ListCount - 1
If UserForm1.cmbEmployee.List(b) > UserForm1.cmbEmployee.List(a) Then
c = UserForm1.cmbEmployee.List(a)
UserForm1.cmbEmployee.List(a) = UserForm1.cmbEmployee.List(b)
UserForm1.cmbEmployee.List(b) = c
End If
Next
Next
ElseIf UserForm1.optEmployeeID = True Then
For i = 2 To tWs.Cells(Rows.Count, 1).End(xlUp).row
x = Application.CountIf(tWs.Range("A" & 1, "A" & i), _
tWs.Cells(i, 1).Value)
If x = 1 Then
UserForm1.cmbEmployee.AddItem tWs.Cells(i, 1) & " - " & tWs.Cells(i, 4)
End If
Next i
For a = 0 To UserForm1.cmbEmployee.ListCount - 1
For b = 0 To UserForm1.cmbEmployee.ListCount - 1
If UserForm1.cmbEmployee.List(b) > UserForm1.cmbEmployee.List(a) Then
c = UserForm1.cmbEmployee.List(a)
UserForm1.cmbEmployee.List(a) = UserForm1.cmbEmployee.List(b)
UserForm1.cmbEmployee.List(b) = c
End If
Next
Next
End If
End If
End Sub
Instead of trying to shape the data using code, I would suggest creating an SQL statement based on runtime logic, opening a recordset with that data, and pushing the result back into the combobox.
Add a reference (Tools -> References...) to Microsoft ActiveX Data Objects; the latest version, usually 6.1.
(Credit goes to CDP1802's answer, which is the basis for much of the logic here.)
Dim source As String
If optInSeat = True Then
source = "'In Seat$'"
ElseIf optTerm = True Then
source = "Terms$"
End If
If Len(source) = 0 Then Exit Sub ' Do nothing
' sort by columns
Dim orderBy As String, expr As String
If optEmployeeName Then
expr = "Trim(F1) & ' - ' & Trim(F4)"
orderBy = "F1, F4"
ElseIf optEmployeeID Then
expr = "Trim(F4) & ' - ' & Trim(F1)"
orderBy = "F4, F1"
Else
expr = "Trim(F1) & ' - ' & Trim(F4)"
End If
Dim connectionString As String
connectionString = _
"Provider=Microsoft.ACE.OLEDB.12.0;" & _
"Data Source=""" & ThisWorkbook.FullName & """;" & _
"Extended Properties=""Excel 12.0;HDR=No"""
Dim sql As String
sql = _
"SELECT " & expr & " " & _
"FROM [" & source & "]"
If Len(orderBy) > 0 Then sql = sql & " ORDER BY " & orderBy
Dim rs As New ADODB.Recordset
rs.Open sql, connectionString
' The 2D array comes back in the wrong direction to be set directly.
' We use WorksheetFunctions.Transpose to switch the direction.
cmbEmployees.List = WorksheetFunction.Transpose(rs.GetRows)
Select unique items using a Dictionary Object and sort them in an array. This sorts in ascending order.
Sub LoadEmployee_Cmb_HC()
Dim wb As Workbook, ws As Worksheet
Dim dict, k As String, i As Long
Dim order(2) As Integer
Set wb = ThisWorkbook
Set dict = CreateObject("Scripting.Dictionary")
' data source
If UserForm1.optInSeat = True Then
Set ws = wb.Sheets("In Seat")
ElseIf UserForm1.optTerm = True Then
Set ws = wb.Sheets("Terms")
End If
' sort by columns
If UserForm1.optEmployeeName = True Then
order(1) = 4: order(2) = 1
ElseIf UserForm1.optEmployeeID = True Then
order(1) = 1: order(2) = 4
End If
If order(1) = 0 Or ws Is Nothing Then
' do nothing
Else
' get unique values start in row 4
For i = 4 To ws.Cells(Rows.Count, order(1)).End(xlUp).Row
k = Trim(ws.Cells(i, order(1)).Value)
If Len(k) > 0 And Not dict.exists(k) Then
dict.Add k, k & " - " & Trim(ws.Cells(i, order(2)))
End If
Next
' sort and populate combo
Call SortCombo(dict, UserForm1.ComboBox1)
End If
End Sub
Sub SortCombo(ByRef dict, cmb As ComboBox)
Dim ar, a As Long, b As Long, i As Long, tmp As String
ar = dict.keys
i = UBound(ar)
For a = 0 To i
For b = a To i
If ar(b) < ar(a) Then
tmp = ar(a)
ar(a) = ar(b)
ar(b) = tmp
End If
Next
ar(a) = dict.Item(ar(a)) ' replace with value after it sort
Next
cmb.List = ar
End Sub
Alternative sort using temporary sheet
Sub SortCombo2(ByRef dict, cmb As ComboBox)
Dim wsTmp As Worksheet, rng As Range, k, ar() As String, i As Long
Set wsTmp = ThisWorkbook.Sheets(3)
wsTmp.Cells.Clear
ReDim ar(dict.Count - 1, 0)
i = 0
For Each k In dict.keys
ar(i, 0) = dict(k)
i = i + 1
Next
Set rng = wsTmp.Range("A1:A" & dict.Count)
rng = ar
With wsTmp.Sort
.SetRange rng
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.Apply
End With
cmb.List = rng.Value2
wsTmp.Cells.Clear
End Sub
Test data generator
Sub data()
Dim ws As Worksheet, i, s, n
Set ws = Sheets("Terms")
ws.Cells.Clear
For i = 4 To 35000
s = ""
For n = 1 To 25
s = s & Chr(65 + Int(Rnd() * 26))
Next
ws.Cells(i, 1) = s
ws.Cells(i, 4) = "D" & i
Next
MsgBox "done " & i - 1
End Sub

Creating ActiveX ComboBoxes Locks Excel

I have the following code that is intended to create a series of combo boxes on a worksheet. Since I cannot be in break mode when creating combo boxes I am struggling to find out what I am doing wrong.
Private Sub CreatePlayerSelectorComboBoxes()
Application.ScreenUpdating = True
Dim currStatusBarMgr As StatusBarManager
Set currStatusBarMgr = New StatusBarManager
currStatusBarMgr.MessagePrefix = "Creating Control: "
With MatchesTeamPlayersWS
.Range(.Cells(17, 1), .Cells(17, .UsedRange.Columns.Count)).ClearContents
Dim matchCounter As Long
For matchCounter = 1 To 6
Dim controlColumnMatch As Long
controlColumnMatch = ((matchCounter - 1) * 24)
Dim matchText As String
matchText = "M" & matchCounter
Dim teamCounter As Long
For teamCounter = 1 To 2
Dim controlColumnTeam As Long
controlColumnTeam = ((teamCounter - 1) * 12)
Dim teamText As String
Select Case teamCounter
Case Is = 1
teamText = "TmA"
Case Is = 2
teamText = "TmB"
End Select
Dim positionCounter As Long
For positionCounter = 1 To 4
Dim positionText As String
positionText = "P" & positionCounter
Dim controlText As String
controlText = matchText & teamText & positionText
Dim currDivAControlName As String
currDivAControlName = "DivA" & controlText
Dim currDivBControlName As String
currDivBControlName = "DivB" & controlText
Dim controlColumnPosition As Long
controlColumnPosition = 3 + ((positionCounter - 1) * 3)
Dim controlColumn As Long
controlColumn = controlColumnMatch + controlColumnTeam + controlColumnPosition
Dim controlCell As Range
Set controlCell = .Cells(17, controlColumn)
currStatusBarMgr.PostStatusBarUpdate (currDivAControlName)
Debug.Print currDivAControlName
Dim controlDivA As Variant
Set controlDivA = .OLEObjects.Add(ClassType:="Forms.ComboBox.1", _
Link:=False, _
DisplayAsIcon:=False, _
Left:=controlCell.Left, _
Top:=controlCell.Top, _
Width:=140, _
Height:=24)
controlDivA.Name = currDivAControlName
currStatusBarMgr.PostStatusBarUpdate (currDivBControlName)
Debug.Print currDivBControlName
Dim controlDivB As Variant
Set controlDivB = .OLEObjects.Add(ClassType:="Forms.ComboBox.1", _
Link:=False, _
DisplayAsIcon:=False, _
Left:=controlCell.Offset(0, 144).Left, _
Top:=controlCell.Offset(0, 144).Top, _
Width:=140, _
Height:=24)
controlDivB.Name = currDivBControlName
Next
Next
Next
End With
Application.ScreenUpdating = True
End Sub
If I comment out the parts that are intended to create the combo boxes the code runs. I have inspected the Set assignments and they appear to be syntactically correct. I have also made sure the names are unique.
I have let Excel run and run.. All I get is [running]... until I force-quit Excel.

Getting a method or data member not found in a class module

Here is the code for the class module (where it errors on the code module):
Private Sub CommandButton_Click()
Dim VBAEditor As VBIDE.VBE
Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent
Dim CodeMod As VBIDE.CodeModule
Dim mdl As Object
Dim mdl_exits As Boolean
Dim mdl_name As String
Dim macro_name As String
Dim macro_exists As Boolean
mdl_name = "SaveButtons"
For Each mdl In ThisWorkbook.VBProject.VBComponents
If mdl.Name = mdl_name And mdl.Type = 1 Then
Set prrf_Module = mdl
mdl_exists = True
Exit For
End If
Next
If mdl_exists Then GoTo it_exists
Set prrf_Module = ThisWorkbook.VBProject.VBComponents.Add(1)
prrf_Module.Name = mdl_name
it_exists:
macro_exists = False
macro_name = SaveButton.Value
new_name:
If macro_exists = True Then
If macro_name = SaveButton.Value Then
macro_name = SaveButton.Value & "1"
Else
macro_name = Left(macro_name, Len(macro_name) - 1) & CInt(Mid(macro_name, Len(macro_name) - 1)) + 1
End If
End If
macro_exists = False
zy = "Userform1.show"
strMacro = "Sub " & "CommandButton1" & vbCr
strMacro = strMacro & " " & zy & vbCr
strMacro = strMacro & "End Sub" & vbCr
Debug.Print "strMacro is " & vbCr & strMacro
'Set prrf_Module = ThisWorkbook.VBProject.VBComponents.Add(1)
Dim d, e, f, y
For y = 1 To 2
With btn_Gen.CodeModule
d = .CountOfLines
.insertlines 1, "Sub CommandButton" & y & "_Click()"
For e = LBound(t) To UBound(t)
countlines = countlines + 1
upper = UBound(t)
xy = countlines + 1
.insertlines xy, " " & t(e)
Next e
.insertlines xy + 1, "End Sub"
End With
Next y
End Sub
And here is my module code (that runs till I click commandbutton1)
Sub showuserform1()
Dim x, y
Dim z() As String
Set btn_nm = New Scripting.Dictionary
Set lbl_tx = New Scripting.Dictionary
Set code = New Scripting.Dictionary
repeatx:
x = UCase(InputBox("(H)orizontal or (V)ertical?", "Orientation", "H"))
If x = "H" Then
orientation = "Horizontal"
ElseIf x = "V" Then
orientation = "Vertical"
Else
MsgBox ("Input either 'H' or 'V'")
GoTo repeatx
End If
repeatcount:
count = InputBox("How many buttons do you want? ", "Button Count", "1")
If count < 1 Then
MsgBox ("Input Quantity of Buttons, enter at least 1")
GoTo repeatcount:
End If
For y = 1 To count
btn_nm(y) = InputBox("What do you want CommandButton" & y + 1 & " to say?", "CommandButton name", "CommandButton" & y)
lbl_tx(y) = InputBox("What do you want Label" & y + 1 & " to say?", "Label text", "Label" & y)
btn_Gen_uf2.Label1.Caption = "Enter Code in Window - max 8k characters"
btn_Gen_uf2.TextBox1.Value = ""
repeatshow:
btn_Gen_uf2.TextBox1.SetFocus
btn_Gen_uf2.Show
If btn_Gen_uf2.TextBox1.Value = "" Then
MsgBox "Enter Code in Window before selecting OK"
GoTo repeatshow:
End If
t = Split(btn_Gen_uf2.TextBox1.Text, vbCrLf)
ReDim z(0 To UBound(t), 1 To count)
For w = LBound(t) To UBound(t)
MsgBox t(w)
'Debug.Print "z(" & w & "," & y & ") = " & t(w)
z(w, y) = t(w)
Next w
Next y
' Do this last
btn_Gen.Show
End Sub
This is crossposted from excelforum.
I added the declarations (see above) and now am getting an error on this line: Dim VBAEditor as VBIDE.VBE
The error is "User-defined type not defined". Am I missing a reference?
With rory's help, I made this one change and most of the code is working.
With Prrf_Module.CodeModule
instead of
With btn_Gen.Codemodule

Argentina supermarket web scraping

I´m trying to scrape data from website:
https://www.disco.com.ar/Comprar/Home.aspx#_atCategory=false&_atGrilla=true&_id=21063
via a macro in Excel 2013, like real-time price, product name and image.
I have tried excel web query but it does not works.
Is there a way of doing this?
There is the example showing how the data could be retrieved from the website using XHRs and JSON parsing, it consists of several steps.
Retrieve the data.
I looked into a little with XHRs using Chrome Developer Tools Network tab.
Most relevant data I found is JSON string returned by POST XHR from https://www.disco.com.ar/Comprar/HomeService.aspx/ObtenerLimiteDeProductos
The POST XHR doesn't work for me without the cookie header. Thus I have to add additional HEAD XHR to retrieve ASP.NET_SessionId cookie first, server version XMLHTTP used to control cookies. The only response headers returning the cookie is GET XHR from https://www.disco.com.ar/Login/PreHome.aspx
Retrieved JSON string should be parsed twice as it contains the second payload JSON wrapped in d property of the first JSON.
Convert parsed JSON object into table-like form presented in 2d-arrays.
Output the arrays to the worksheet. You can perform further processing with direct access to the arrays.
For the webpage shown below:
The output for me is as follows:
Put the below code into VBA Project standard module:
Option Explicit
Sub GetData()
Dim sCookie As String
Dim sPayLoad As String
Dim sCont As String
Dim vJSON As Variant
Dim sState As String
Dim y As Long
Dim sSection As Variant
Dim aData()
Dim aHeader()
' Get cookie from the site
With CreateObject("MSXML2.ServerXMLHTTP")
.Open "HEAD", "https://www.disco.com.ar/Login/PreHome.aspx", False
.Send
sCookie = .getAllResponseHeaders
End With
sCookie = Split(sCookie, "Set-Cookie: ", 2)(1)
sCookie = Split(sCookie, ";", 2)(0)
' Retrieve JSON data
sPayLoad = "{IdMenu:""21063"",textoBusqueda:"""", producto:"""", marca:"""", " & _
"pager:"""", ordenamiento:0, precioDesde:"""", precioHasta:""""}"
With CreateObject("MSXML2.ServerXMLHTTP")
.Open "POST", "https://www.disco.com.ar/Comprar/HomeService.aspx/ObtenerArticulosPorDescripcionMarcaFamiliaLevex", False
.SetRequestHeader "Accept", "application/json, text/javascript, */*; q=0.01"
.SetRequestHeader "Content-Type", "application/json; charset=utf-8"
.SetRequestHeader "Content-Length", Len(sPayLoad)
.SetRequestHeader "Cookie", sCookie
.Send CStr(sPayLoad)
sCont = .responseText
End With
' Parse JSON response
JSON.Parse sCont, vJSON, sState
sCont = vJSON.Item("d")
JSON.Parse sCont, vJSON, sState
' Output tables
Sheets(1).Cells.Delete
y = 1
For Each sSection In Array("Tipo", "Marca", "Precio", "ResultadosBusquedaLevex", "ArticulosSugereridos")
JSON.ToArray vJSON.Item(sSection), aData, aHeader
With Sheets(1)
.Cells(y, 1).Value = sSection
OutputArray .Cells(y + 1, 1), aHeader
Output2DArray .Cells(y + 2, 1), aData
.Cells.Columns.AutoFit
End With
y = y + UBound(aData, 1) + 4
Next
End Sub
Sub OutputArray(oDstRng As Range, aCells As Variant)
With oDstRng
.Parent.Select
With .Resize( _
1, _
UBound(aCells) - LBound(aCells) + 1)
.NumberFormat = "#"
.Value = aCells
End With
End With
End Sub
Sub Output2DArray(oDstRng As Range, aCells As Variant)
With oDstRng
.Parent.Select
With .Resize( _
UBound(aCells, 1) - LBound(aCells, 1) + 1, _
UBound(aCells, 2) - LBound(aCells, 2) + 1)
.NumberFormat = "#"
.Value = aCells
End With
End With
End Sub
Create one more standard module, name it JSON and put the below code into it, this code provides JSON processing functionality:
Option Explicit
Private sBuffer As String
Private oTokens As Object
Private oRegEx As Object
Private bMatch As Boolean
Private oChunks As Object
Private oHeader As Object
Private aData() As Variant
Private i As Long
Sub Parse(ByVal sSample As String, vJSON As Variant, sState As String)
' Backus–Naur form JSON parser implementation based on RegEx
' Input:
' sSample - source JSON string
' Output:
' vJson - created object or array to be returned as result
' sState - string Object|Array|Error depending on processing
sBuffer = sSample
Set oTokens = CreateObject("Scripting.Dictionary")
Set oRegEx = CreateObject("VBScript.RegExp")
With oRegEx ' Patterns based on specification http://www.json.org/
.Global = True
.MultiLine = True
.IgnoreCase = True ' Unspecified True, False, Null accepted
.Pattern = "(?:'[^']*'|""(?:\\""|[^""])*"")(?=\s*[,\:\]\}])" ' Double-quoted string, unspecified quoted string
Tokenize "s"
.Pattern = "[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:e[+-]?\d+)?(?=\s*[,\]\}])" ' Number, E notation number
Tokenize "d"
.Pattern = "\b(?:true|false|null)(?=\s*[,\]\}])" ' Constants true, false, null
Tokenize "c"
.Pattern = "\b[A-Za-z_]\w*(?=\s*\:)" ' Unspecified non-double-quoted property name accepted
Tokenize "n"
.Pattern = "\s+"
sBuffer = .Replace(sBuffer, "") ' Remove unnecessary spaces
.MultiLine = False
Do
bMatch = False
.Pattern = "<\d+(?:[sn])>\:<\d+[codas]>" ' Object property structure
Tokenize "p"
.Pattern = "\{(?:<\d+p>(?:,<\d+p>)*)?\}" ' Object structure
Tokenize "o"
.Pattern = "\[(?:<\d+[codas]>(?:,<\d+[codas]>)*)?\]" ' Array structure
Tokenize "a"
Loop While bMatch
.Pattern = "^<\d+[oa]>$" ' Top level object structure, unspecified array accepted
If .Test(sBuffer) And oTokens.Exists(sBuffer) Then
Retrieve sBuffer, vJSON
sState = IIf(IsObject(vJSON), "Object", "Array")
Else
vJSON = Null
sState = "Error"
End If
End With
Set oTokens = Nothing
Set oRegEx = Nothing
End Sub
Private Sub Tokenize(sType)
Dim aContent() As String
Dim lCopyIndex As Long
Dim i As Long
Dim sKey As String
With oRegEx.Execute(sBuffer)
If .Count = 0 Then Exit Sub
ReDim aContent(0 To .Count - 1)
lCopyIndex = 1
For i = 0 To .Count - 1
With .Item(i)
sKey = "<" & oTokens.Count & sType & ">"
oTokens(sKey) = .Value
aContent(i) = Mid(sBuffer, lCopyIndex, .FirstIndex - lCopyIndex + 1) & sKey
lCopyIndex = .FirstIndex + .Length + 1
End With
Next
End With
sBuffer = Join(aContent, "") & Mid(sBuffer, lCopyIndex, Len(sBuffer) - lCopyIndex + 1)
bMatch = True
End Sub
Private Sub Retrieve(sTokenKey, vTransfer)
Dim sTokenValue As String
Dim sName As String
Dim vValue As Variant
Dim aTokens() As String
Dim i As Long
sTokenValue = oTokens(sTokenKey)
With oRegEx
.Global = True
Select Case Left(Right(sTokenKey, 2), 1)
Case "o"
Set vTransfer = CreateObject("Scripting.Dictionary")
aTokens = Split(sTokenValue, "<")
For i = 1 To UBound(aTokens)
Retrieve "<" & Split(aTokens(i), ">", 2)(0) & ">", vTransfer
Next
Case "p"
aTokens = Split(sTokenValue, "<", 4)
Retrieve "<" & Split(aTokens(1), ">", 2)(0) & ">", sName
Retrieve "<" & Split(aTokens(2), ">", 2)(0) & ">", vValue
If IsObject(vValue) Then
Set vTransfer(sName) = vValue
Else
vTransfer(sName) = vValue
End If
Case "a"
aTokens = Split(sTokenValue, "<")
If UBound(aTokens) = 0 Then
vTransfer = Array()
Else
ReDim vTransfer(0 To UBound(aTokens) - 1)
For i = 1 To UBound(aTokens)
Retrieve "<" & Split(aTokens(i), ">", 2)(0) & ">", vValue
If IsObject(vValue) Then
Set vTransfer(i - 1) = vValue
Else
vTransfer(i - 1) = vValue
End If
Next
End If
Case "n"
vTransfer = sTokenValue
Case "s"
vTransfer = Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace( _
Mid(sTokenValue, 2, Len(sTokenValue) - 2), _
"\""", """"), _
"\\", "\"), _
"\/", "/"), _
"\b", Chr(8)), _
"\f", Chr(12)), _
"\n", vbLf), _
"\r", vbCr), _
"\t", vbTab)
.Global = False
.Pattern = "\\u[0-9a-fA-F]{4}"
Do While .Test(vTransfer)
vTransfer = .Replace(vTransfer, ChrW(("&H" & Right(.Execute(vTransfer)(0).Value, 4)) * 1))
Loop
Case "d"
vTransfer = Evaluate(sTokenValue)
Case "c"
Select Case LCase(sTokenValue)
Case "true"
vTransfer = True
Case "false"
vTransfer = False
Case "null"
vTransfer = Null
End Select
End Select
End With
End Sub
Function Serialize(vJSON As Variant) As String
Set oChunks = CreateObject("Scripting.Dictionary")
SerializeElement vJSON, ""
Serialize = Join(oChunks.Items(), "")
Set oChunks = Nothing
End Function
Private Sub SerializeElement(vElement As Variant, ByVal sIndent As String)
Dim aKeys() As Variant
Dim i As Long
With oChunks
Select Case VarType(vElement)
Case vbObject
If vElement.Count = 0 Then
.Item(.Count) = "{}"
Else
.Item(.Count) = "{" & vbCrLf
aKeys = vElement.Keys
For i = 0 To UBound(aKeys)
.Item(.Count) = sIndent & vbTab & """" & aKeys(i) & """" & ": "
SerializeElement vElement(aKeys(i)), sIndent & vbTab
If Not (i = UBound(aKeys)) Then .Item(.Count) = ","
.Item(.Count) = vbCrLf
Next
.Item(.Count) = sIndent & "}"
End If
Case Is >= vbArray
If UBound(vElement) = -1 Then
.Item(.Count) = "[]"
Else
.Item(.Count) = "[" & vbCrLf
For i = 0 To UBound(vElement)
.Item(.Count) = sIndent & vbTab
SerializeElement vElement(i), sIndent & vbTab
If Not (i = UBound(vElement)) Then .Item(.Count) = "," 'sResult = sResult & ","
.Item(.Count) = vbCrLf
Next
.Item(.Count) = sIndent & "]"
End If
Case vbInteger, vbLong
.Item(.Count) = vElement
Case vbSingle, vbDouble
.Item(.Count) = Replace(vElement, ",", ".")
Case vbNull
.Item(.Count) = "null"
Case vbBoolean
.Item(.Count) = IIf(vElement, "true", "false")
Case Else
.Item(.Count) = """" & _
Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(vElement, _
"\", "\\"), _
"""", "\"""), _
"/", "\/"), _
Chr(8), "\b"), _
Chr(12), "\f"), _
vbLf, "\n"), _
vbCr, "\r"), _
vbTab, "\t") & _
""""
End Select
End With
End Sub
Function ToString(vJSON As Variant) As String
Select Case VarType(vJSON)
Case vbObject, Is >= vbArray
Set oChunks = CreateObject("Scripting.Dictionary")
ToStringElement vJSON, ""
oChunks.Remove 0
ToString = Join(oChunks.Items(), "")
Set oChunks = Nothing
Case vbNull
ToString = "Null"
Case vbBoolean
ToString = IIf(vJSON, "True", "False")
Case Else
ToString = CStr(vJSON)
End Select
End Function
Private Sub ToStringElement(vElement As Variant, ByVal sIndent As String)
Dim aKeys() As Variant
Dim i As Long
With oChunks
Select Case VarType(vElement)
Case vbObject
If vElement.Count = 0 Then
.Item(.Count) = "''"
Else
.Item(.Count) = vbCrLf
aKeys = vElement.Keys
For i = 0 To UBound(aKeys)
.Item(.Count) = sIndent & aKeys(i) & ": "
ToStringElement vElement(aKeys(i)), sIndent & vbTab
If Not (i = UBound(aKeys)) Then .Item(.Count) = vbCrLf
Next
End If
Case Is >= vbArray
If UBound(vElement) = -1 Then
.Item(.Count) = "''"
Else
.Item(.Count) = vbCrLf
For i = 0 To UBound(vElement)
.Item(.Count) = sIndent & i & ": "
ToStringElement vElement(i), sIndent & vbTab
If Not (i = UBound(vElement)) Then .Item(.Count) = vbCrLf
Next
End If
Case vbNull
.Item(.Count) = "Null"
Case vbBoolean
.Item(.Count) = IIf(vElement, "True", "False")
Case Else
.Item(.Count) = CStr(vElement)
End Select
End With
End Sub
Sub ToArray(vJSON As Variant, aRows() As Variant, aHeader() As Variant)
' Input:
' vJSON - Array or Object which contains rows data
' Output:
' aData - 2d array representing JSON data
' aHeader - 1d array of property names
Dim sName As Variant
Set oHeader = CreateObject("Scripting.Dictionary")
Select Case VarType(vJSON)
Case vbObject
If vJSON.Count > 0 Then
ReDim aData(0 To vJSON.Count - 1, 0 To 0)
oHeader("#") = 0
i = 0
For Each sName In vJSON
aData(i, 0) = "#" & sName
ToArrayElement vJSON(sName), ""
i = i + 1
Next
Else
ReDim aData(0 To 0, 0 To 0)
End If
Case Is >= vbArray
If UBound(vJSON) >= 0 Then
ReDim aData(0 To UBound(vJSON), 0 To 0)
For i = 0 To UBound(vJSON)
ToArrayElement vJSON(i), ""
Next
Else
ReDim aData(0 To 0, 0 To 0)
End If
Case Else
ReDim aData(0 To 0, 0 To 0)
aData(0, 0) = ToString(vJSON)
End Select
aHeader = oHeader.Keys()
Set oHeader = Nothing
aRows = aData
Erase aData
End Sub
Private Sub ToArrayElement(vElement As Variant, sFieldName As String)
Dim sName As Variant
Dim j As Long
Select Case VarType(vElement)
Case vbObject ' collection of objects
For Each sName In vElement
ToArrayElement vElement(sName), sFieldName & IIf(sFieldName = "", "", "_") & sName
Next
Case Is >= vbArray ' collection of arrays
For j = 0 To UBound(vElement)
ToArrayElement vElement(j), sFieldName & IIf(sFieldName = "", "", "_") & "#" & j
Next
Case Else
If Not oHeader.Exists(sFieldName) Then
oHeader(sFieldName) = oHeader.Count
If UBound(aData, 2) < oHeader.Count - 1 Then ReDim Preserve aData(0 To UBound(aData, 1), 0 To oHeader.Count - 1)
End If
j = oHeader(sFieldName)
aData(i, j) = ToString(vElement)
End Select
End Sub
Check VBA-JSON-parser on GitHub for the latest version of JSON parser (import JSON.bas module into the VBA project for JSON processing).
I would suggest you to use Selenium with Python.
It will take a while to configure, but once its done you will have the perfect tool for the job.
It will let you scrape any website you need using simple Python syntax, using the web-browser you want (Firefox or Chrome), reading and interacting with javascript.
I use it on daily basis.

mixing subscript in a string

in a VBA excel macro I am using, I have the following code:
For k = MinDeg To MaxDeg
OutputStr = Trim(OutputStr & "a" & Str(k) & " = " & _
Str(MyCoe) & " ± " & _
Str(MyErr) & Chr(10))
Next k
Where "MyCoe" and "MyErr" are given numbers, and "minDeg" and "MaxDeg" are integers.
My question is:
How can I make "Str(k)" appear in the outputstr as subscript text?
If Unicode is available in your environment, another option would be to use the subscripted Unicode characters for Str(K). Making some modifications to Gary's Student code so as to get output in A1:
Option Explicit
Sub foo()
Dim K As Long
Const MinDeg As Long = 10
Const MaxDeg As Long = 13
Dim sK As String, I As Long
Const MyCoe As Long = 3
Const MyErr As Long = 5
Dim OutPutStr As String
For K = MinDeg To MaxDeg
sK = ""
For I = 1 To Len(CStr(K))
sK = sK & ChrW(832 & Mid(CStr(K), I, 1))
Next I
OutPutStr = Trim(OutPutStr & "a" & sK & " = " & _
Str(MyCoe) & " ± " & _
Str(MyErr) & Chr(10))
Next K
Cells(1, 1) = OutPutStr
End Sub
Note that the subscripted values also appear in the formula bar.
First I run this simple mod to your code:
Sub WhatEverr()
mindeg = 10
maxdeg = 13
mycoe = 3
myerr = 5
For k = mindeg To maxdeg
outputstr = Trim(outputstr & "a" & Str(k) & " = " & _
Str(mycoe) & " ± " & _
Str(myerr) & Chr(10))
Next k
Range("A1").Value = outputstr
End Sub
to get this in A1:
Then I run:
Sub formatcell()
Dim i As Long, L As Long, rng As Range
Dim s As String
Set rng = Range("A1")
s = rng.Value
L = Len(s)
For i = 1 To L
ch = Mid(s, i, 1)
If ch = "a" Then
rng.Characters(Start:=i + 2, Length:=2).Font.Subscript = True
End If
Next i
End Sub
To apply the format:
In Excel, this type of character formatting is a property of the Range object. You do not build it into the string like you would in HTML.

Resources