Porting VBA To IronPython - vba

I am trying to translate the VBA code found in this link into IronPython. Can anyone recommend a good VBA resource to explain how best to do this for a Python programmer?
I have all the Excel portions implemented, such as the treatment and use of objects, workbooks, worksheets, etc.
I will also settle for an explanation of this snippet of code:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Put the index value of the sheet into Arr. Ensure there
' are no duplicates. If Arr(N) is not zero, we've already
' loaded that element of Arr and thus have duplicate sheet
' names.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
If Arr(N) > 0 Then
ErrorText = "Duplicate worksheet name in NameArray."
SortWorksheetsByNameArray = False
Exit Function
End If
why would Arr(N) ever NOT be greater than 0?
Here is my current code, which is broken:
def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
temp_list = []
for n in range(len(name_list)):
if name_list.count(name_list[n]) > 1:
raise Exception("Duplicate worksheet name in NameArray.")
else:
temp_list.append(wb.Worksheets(name_list[n]).Index)
for m in range(len(temp_list)):
for n in range(m, len(temp_list)):
if temp_list[n] < temp_list[m]:
l = temp_list[n]
temp_list[n] = temp_list[m]
temp_list[m] = l
if not all(temp_list[i] <= temp_list[i+1] for i in xrange(len(temp_list)-1)):
return False
print "current order"
for sheet in wb.Worksheets:
print sheet.Name
wb.Worksheets(name_list[0]).Move(Before=wb.Worksheets(1))
#WB.Worksheets(NameArray(LBound(NameArray))).Move before:=WB.Worksheets(Arr(1))
for n in range(len(name_list)-1):
print 'moving ', name_list[n], 'before ', name_list[n+1]
wb.Worksheets(name_list[n]).Move(Before=wb.Worksheets(name_list[n + 1]))
Note:
With this answer as reference, here is all I had to do:
def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
l = []
# since wb.Worksheets is a com_object, you can't use the "if _ in XXX"
# construct without converting to a list first
for s in wb.Worksheets:
l.append(s.Name)
for n in range(len(name_list)):
if name_list[n] in l:
wb.Worksheets(name_list[n]).Move(After=wb.Worksheets(wb.Worksheets.Count))

Declaring and dimensioning an array of longs in VBA will create the array with the default value of 0 in each slot.

def move_worksheets_according_to_list(self, name_list):
wb = self.com_workbook
l = []
# since wb.Worksheets is a com_object, you can't use the "if _ in XXX"
# construct without converting to a list first
for s in wb.Worksheets:
l.append(s.Name)
for n in range(len(name_list)):
if name_list[n] in l:
wb.Worksheets(name_list[n]).Move(After=wb.Worksheets(wb.Worksheets.Count)

Related

Syntax error when passing variable args in macro

I have a macro for libreoffice that takes in 2 arguments.
A file path
Variable args that are sheet names to look for.
I iterate over the document pertaining to the first arg and get the sheet that matches the first variable arg. I then convert that sheet to CSV.
Sub ExportToCsv(URL as String, ParamArray sheetNames() As Variant)
Dim saveParams(1) as New com.sun.star.beans.PropertyValue
saveParams(0).Name = "FilterName"
saveParams(0).Value = "Text - txt - csv (StarCalc)"
saveParams(1).Name = "FilterOptions"
saveParams(1).Value = "44,34,0,1,1" ' 44=comma, 34=double-quote
GlobalScope.BasicLibraries.loadLibrary("Tools")
URL = ConvertToURL(URL)
document = StarDesktop.loadComponentFromUrl(URL, "_blank", 0, Array())
baseName = Tools.Strings.GetFileNameWithoutExtension(document.GetURL(), "/")
directory = Tools.Strings.DirectoryNameoutofPath(document.GetURL(), "/")
sheets = document.Sheets
sheetCount = sheets.Count
Dim x as Integer
Dim requiredSheetIndex as Integer
For x = 0 to sheetCount -1
sheet = sheets.getByIndex(x)
sheet.isVisible = True
For i = LBound(sheetNames) To UBound(sheetNames)
If StrComp(sheet.Name, sheetNames(i), vbTextCompare) = 0 Then
requiredSheetIndex = x
End If
Next
Next
currentSheet = document.GetCurrentController.GetActiveSheet()
sheet = sheets(requiredSheetIndex)
document.GetCurrentController.SetActiveSheet(sheet)
filename = directory + "/" + baseName + ".csv"
fileURL = convertToURL(Filename)
document.StoreToURL(fileURL, saveParams())
document.close(True)
End Sub
Eg. ExportToCsv(<path>, 'Data'). Suppose the document has 4 sheets with the 3rd sheet as DATA, this sheet should be converted to CSV.
Previously I used to give the sheet idnex directly into the macro and it worked perfectly. But requirements changed and I have to pass in an array of possible names to match on. Hence the variable args.
But now I am getting a syntax error(Screenshot attached). I cant figure out what is wrong here.
I believe it is complaining about the ParamArray keyword. After a little digging, I discovered you need to include:
option compatible
at the top of your module. For more information, you can refer to this link.

Pandas, Copy cells from workbook to another

I am having issues copying cells from excel workbook and pasting as values to another workbook.
I get an error on line rowSelected.append(sheet.cell(row = i, column = j).value) with the message AttributeError: 'str' object has no attribute 'cell'
Can anyone help with this?
import openpyxl
#Prepare the spreadsheets to copy from and paste too.
#File to be copied
wb = openpyxl.load_workbook(r"") #Add file name
sheet = wb["BusinessDetails"] #Add Sheet name
#File to be pasted into
template = openpyxl.load_workbook(r"") #Add file name
temp_sheet = template["Sheet1"] #Add Sheet name
#Copy range of cells as a nested list
#Takes: start cell, end cell, and sheet you want to copy from.
def copyRange(startCol, startRow, endCol, endRow, sheet):
rangeSelected = []
#Loops through selected Rows
for i in range(startRow,endRow + 1,1):
#Appends the row to a RowSelected list
rowSelected = []
for j in range(startCol,endCol+1,1):
rowSelected.append(sheet.cell(row = i, column = j).value)
#Adds the RowSelected List and nests inside the rangeSelected
rangeSelected.append(rowSelected)
return rangeSelected
#Paste range
#Paste data from copyRange into template sheet
def pasteRange(startCol, startRow, endCol, endRow, sheetReceiving,copiedData):
countRow = 0
for i in range(startRow,endRow+1,1):
countCol = 0
for j in range(startCol,endCol+1,1):
sheetReceiving.cell(row = i, column = j).value = copiedData[countRow][countCol]
countCol += 1
countRow += 1
def createData():
print("Processing...")
selectedRange = copyRange(1,2,4,14,sheet) #Change the 4 number values
pastingRange = pasteRange(1,3,4,15,temp_sheet,selectedRange) #Change the 4 number values
#You can save the template as another file to create a new file here too.s
template.save(r"")
print("Range copied and pasted!")
copyRange(2,4,30,78,"BusinessDetails")
pasteRange(2,4,30,78,"Sheet1")
It must be:
copyRange(2,4,30,78,sheet)
pasteRange(2,4,30,78,temp_sheet)
i.e. you need to pass the sheet objects, not the sheet names to your functions.
Update as per comment:
rangeSelected = copyRange(2,4,30,78,sheet)
pasteRange(2,4,30,78,temp_sheet, rangeSelected)

Pass user input from excel cells to an Array

I am very new to VBA, so I apologize if this is a very simple question. I am trying to pass user input data into an array. Actually, 4 different arrays. All 4 arrays can have up to 3 elements, but could only need one at any given time. They are then sorted a specific way via For Loops and then will output the sendkeys function to the active window (which will not be excel when it is running). I have the for loops figured out and it is sorting the way i need it to. I just need to be able to get the user input into those arrays and then output them to a phantom keyboard (i.e. sendkeys). I appreciate any help or advice!
FYI, I have declared the arrays as strings and the variables as long... the message boxes are there to just test the sort, they are not very important
For i = 0 To UBound(SheetPosition)
If j = UBound(Position) Then
j = 0
End If
For j = 0 To UBound(Position)
If k = UBound(Direction) Then
k = 0
End If
For k = 0 To UBound(Direction)
If l = UBound(Temper) Then
l = 0
End If
For l = 0 To UBound(Temper)
MsgBox(i)
MsgBox(SheetPosition(i))
MsgBox(j)
MsgBox(Position(j))
MsgBox(k)
MsgBox(Direction(k))
MsgBox(l)
MsgBox(Temper(l))
Next
Next
Next
Next
you could use Application.InputBox() method in two ways:
Dim myArray As Variant
myArray = Application.InputBox("List the values in the following format: " & vbCrLf & "{val1, val2, val3, ...}", Type:=64) '<--| this returns an array of 'Variant's
myArray = Split(Application.InputBox("List the values in the following format: " & vbCrLf & "val1, val2, val3, ...", Type:=2), ",") '<--| this returns an array of 'String's
Yes, you could get the input from the user using Input boxes:
myValue = InputBox("Give me some input")
Or forms, which is the preferred method. Unfortunately, forms take some time to develop and are best deployed through Excel add-ins, which also require time to learn how to setup.
Here is a good tutorial on using the SendKeys method:
http://www.contextures.com/excelvbasendkeys.html
The usual way of getting data from cells into an array would be:
Dim SheetPosition As Variant
SheetPosition = Range("A1:A3").Value
or perhaps
Dim SheetPosition As Variant
SheetPosition = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value
A few things to note:
The array needs to be dimensioned as a Variant.
The dimension of the array will be rows x columns, so in the first example above SheetPosition will be dimensioned 1 To 3, 1 To 1, and in the second example it might be dimensioned 1 To 5721, 1 To 1 (if the last non-empty cell in column A was A5721)
If you need to find the dimensions of a multi-dimensioned array, you should use UBound(SheetPosition, 1) to find the upper bound of the first dimension and UBound(SheetPosition, 2) to find the upper bound of the second dimension.
Even if you include Option Base 0 at the start of your code module, the arrays will still be dimensioned with a lower bound of 1.
If you want a single dimensioned array and your user input is in a column, you can use Application.Transpose to achieve this:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Range("A1:A3").Value)
In this case SheetPosition will be dimensioned 1 To 3.
If you want a single dimensioned array and your user input is in a row, you can still use Application.Transpose to achieve this, but you have to use it twice:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Application.Transpose(Range("A1:C1").Value))
FWIW - Your If statements in the code in the question are not achieving anything - each of the variables that are being set to 0 are going to be set to 0 by the following For statements anyway. So your existing code could be:
For i = LBound(SheetPosition) To UBound(SheetPosition)
For j = LBound(Position) To UBound(Position)
For k = LBound(Direction) To UBound(Direction)
For l = LBound(Temper) To UBound(Temper)
MsgBox i
MsgBox SheetPosition(i)
MsgBox j
MsgBox Position(j)
MsgBox k
MsgBox Direction(k)
MsgBox l
MsgBox Temper(l)
Next
Next
Next
Next

When does VBA change variable type without being asked to?

I am getting a runtime error I don't understand in Excel 2011 for Mac under OS X 10.7.5. Here is a summary of the code:
Dim h, n, k as Integer
Dim report as Workbook
Dim r1 as Worksheet
Dim t, newline as String
Dim line() as String
newline = vbCr
'
' (code to get user input from a text box, to select a worksheet by number)
'
ReDim line(report.Sheets.Count + 10)
MsgBox "Array line has " & UBound(line) & " elements." '----> 21 elements
line = split(t, newline)
h = UBound(line)
MsgBox "Array line has " & h & " elements." '----> 16 elements
n = 0
MsgBox TypeName(n) '----> Integer
For k = h To 1 Step -1
If IsNumeric(line(k)) Then
n = line(k)
Exit For
End If
Next k
If n > 0 Then
MsgBox n '----> 7
MsgBox TypeName(n) '----> String
Set r1 = report.Sheets(n) '----> Runtime error "Subscript out of bounds"
So n is declared as an integer, but now VBA thinks it is a string and looks for a worksheet named "7". Is this a platform bug, or is there something I haven't learned yet?
It also surprises me that putting data into the dynamic array reduces its dimension, but perhaps that is normal, or perhaps for dynamic arrays Ubound returns the last used element instead of the dimension, although I have not seen that documented.
The first part of your question is answered by #ScottCraner in the comments - the correct syntax for declaring multiple strongly typed variables on one line is:
Dim h As Integer, n As Integer, k As Integer
'...
Dim t As String, newline As String
So, I'll address the second part of your question specific to UBound - unless you've declared Option Base 1 at the top of the module, your arrays start at element 0 by default, not element 1. However, the Split function always returns a 0 based array (unless you split a vbNullString, in which case you get a LBound of -1):
Private Sub ArrayBounds()
Dim foo() As String
'Always returns 3, regardless of Option Base:
foo = Split("zero,one,two,three", ",")
MsgBox UBound(foo)
ReDim foo(4)
'Option Base 1 returns 1,4
'Option Base 0 (default) returns 0,3
MsgBox LBound(foo) & "," & UBound(foo)
End Sub
That means this line is extremely misleading...
h = UBound(line)
MsgBox "Array line has " & h & " elements."
...because the Array line actually has h + 1 elements, which means that your loop here...
For k = h To 1 Step -1
If IsNumeric(line(k)) Then
n = line(k)
Exit For
End If
Next k
...is actually skipping element 0. You don't really even need the h variable at all - you can just make your loop parameter this...
For k = UBound(line) To LBound(line) Step -1
If IsNumeric(line(k)) Then
n = line(k)
Exit For
End If
Next k
...and not have to worry what the base of the array is.
BTW, not asked, but storing vbCr as a variable here...
newline = vbCr
...isn't necessary at all, and opens the door for all kinds of other problems if you intend that a "newline" is always vbCr. Just use the pre-defined constant vbCr directly.

VBA Filter Function for dynamic array doesn't seem to be filtering on occasion

I am writing a subroutine in VBA to cycle through all the listed job numbers in a multi-tab time sheet and create a list of all job numbers that have been used (so it takes the original list (with possibly multiple job number occurrences) and creates a list with only one occurrence of each job number. The job numbers on each sheet are found in range("A8:A30"). The code below seems to work for the first several job names on the sample that I'm testing, but then seems to stop filtering. A8:A21 of the first sheet is:
14GCI393
14GCI393
13GCI373
13GCI373
13GCI388
13GCI367:2
14GCI408
14GCI408
13GCI373
13GCI388
14GCI415
14GCI415
00GCI000
And the code is:
Sub listusedjobs()
Dim usedjobs() As String
Dim nextjob As String
Dim i, m, n, lastsheetindexnumber As Integer
Application.ScreenUpdating = False
lastsheetindexnumber = ThisWorkbook.Sheets.Count
m = 0
ReDim usedjobs(m)
usedjobs(m) = "initialize"
For i = 1 To lastsheetindexnumber
Sheets(i).Activate
For n = 8 To 30
nextjob = Range("A" & n).Value
If Not IsInArray(nextjob, usedjobs) Then 'determine if nextjob is already in usedjobs()
ReDim usedjobs(m)
usedjobs(m) = nextjob 'Add each unique job to array "usedjobs"
Sheets(lastsheetindexnumber).Cells(m + 40, 1).Value = nextjob 'Print job name that was just added
m = m + 1
End If
Next n
Next i
Application.ScreenUpdating = True
End Sub
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = (UBound(Filter(arr, stringToBeFound, , vbTextCompare)) > -1)
End Function
Any help figuring out what is going wrong will be much appreciated! The current output I get for this code is below and contains multiple doubles.
14GCI393
13GCI373
13GCI388
13GCI367:2
14GCI408
13GCI373
13GCI388
14GCI415
00GCI000
I think that your problem may be not using ReDim Preserve inside your If Not