I'm trying to ReDim an array in QTP/VB Script but I keep getting the error: Expected Statement.
I've been over the code a hundred times in the last few hours and I'm just not seeing what is missing, and nobody in the office here seems to know what is missing.
So this is just a sanity check for me at this point, and get a fresh set of eyes on the problem.
Here is the line of code with the error message:
ReDim strTempArray(LBound(AryVar) To (UBound(AryVar) - 1))
Or I could put it this way, I've tried so many variations on the theme:
ReDim strTempArray(0 To AryVar.Length - 1)
But they all have one thing in common, they all give the same meaningless error:
Expected statement on that one line of code.
Alright, so maybe it's something with the rest of the sub, so here is the full sub code.
Basically it's part of a Sub called RemoveArrayElement:
Public Sub RemoveArrayElement(AryVar(), intIndexToRemove)
strTempArray()
lngX = 0 'As Long
lngDestinationIndex = 0 'As Long
ReDim strTempArray(LBound(AryVar) To (UBound(AryVar) - 1))
'ReDim strTempArray(0 To AryVar.Length - 1)
lngDestinationIndex = 0
For lngX = LBound(AryVar) To UBound(AryVar)
If lngX <> intIndexToRemove Then
strTempArray(lngDestinationIndex) = AryVar(lngX)
lngDestinationIndex = lngDestinationIndex + 1
End If
Next
AryVar = strTempArray
'End If
End Sub
I should also add that I did include the Option Explicit at the beginning of the file.
Commenting out that one line that is giving the error does make the error go away, which seems to imply for me that the error really is on that line of code and not some place else.
So what am I missing?
Thanks in advance!
VBScript arrays are strictly zero-based, so specifying a range (.. To ..) makes no sense and isn't supported (in contrast to other Basic dialects). Just use ReDim Array(UBound) and be careful with your indices.
Related
I understand that it's possible to iterate over SubFolders using a For Each loop, as follows:
for each f in myfolder.SubFolders
WScript.Echo f.ShortName
next
However, I would like to iterate by index. I can't seem to get it to work, nor find any examples online. This is what I have tried:
for i = 1 to myfolder.SubFolders.Count
WScript.Echo myfolder.SubFolders.Item(i).ShortName
next
Any suggestions?
EDIT
Here is the use case:
For at least one of my subfolders, I get a Permission Denied error which happens on the for each line. As such I need to wrap the whole for loop in error handling:
on error resume next
for each f in folderobj.SubFolders
' do some stuff
next
on error goto 0
What I want to do is finer grained more controlled error handling, like this:
for i = 0 to folderobj.SubFolders.Count - 1
on error resume next
' access folder
if Err.Number = RELEVANT_ERROR_NUMBER then
' do something sensible
end if
next
As #Lankymart already pointed out the Folders collection returned by the SubFolders property doesn't allow access by index, only by name. If you need indexed access you will have to put the folder objects into an array first:
ReDim sf(myfolder.SubFolders.Count - 1)
i = 0
For Each f In myfolder.SubFolders
Set sf(i) = f
i = i + 1
Next
Then you can access the elements in sf by index.
For i = 0 To UBound(sf)
WScript.Echo sf(i).ShortName
Next
Note that VBScript arrays are zero-based, so the index will run from 0 to .SubFolders.Count-1.
Note also that if anything changes the collection you must re-synchronize the array with the collection yourself. It won't pick up changes automagically.
Here's part of my code.
<%
Dim lineData,fso,filea,fileb,filec
s=request.querystring("query")
set fso = Server.CreateObject("Scripting.FileSystemObject")
a(0,0)=0
a(1,0)=" - Entries in File A"
set filea = fso.OpenTextFile(Server.MapPath("FileA.txt"), 1, true)
do until lone.AtEndOfStream
lineData = lcase(filea.ReadLine())
if instr(lineData,s)>0 then
a(0,0)=a(0,0)+1
end if
Loop
a(0,1)=0
a(1,1)=" - Entries in File B"
set fileb = fso.OpenTextFile(Server.MapPath("FileB.txt"), 1, true)
do until mile.AtEndOfStream
lineData = lcase(fileb.ReadLine())
if instr(lineData,s)>0 then
a(0,1)=a(0,1)+1
end if
Loop
a(0,2)=0
a(1,2)=" - Entries in File C"
set filec = fso.OpenTextFile(Server.MapPath("FileC.txt"), 1, true)
do until payne.AtEndOfStream
lineData = lcase(filec.ReadLine())
if instr(lineData,s)>0 then
a(0,2)=a(0,2)+1
end if
Loop
%>
The code essentially looks for the number of entries in a text file. What I need is it to be sorted such that the file with the most number of entries comes first.
Suppose there are 10 entries in FileA, 12 in FileB and 7 in FileC. I'd like the output to be displayed like this:
12 - Entries in File B
10 - Entries in File A
7 - Entries in File C
I'm guessing it won't be too complicated since response.write(a(0,i)&a(1,i)) will work. I just need help with the loop or any sorting method if there is one.
Any help I can get in here will be much appreciated.
This will be a "neo-answer" that should help you get to where you want to go, both in the short- and long-term.
1) First, a suggestion for further reading to help you address this sort of problem in a more general way -- and to help you develop your "chops" as you go. You can Google the term "bubble sort" and get a whole host of interesting and mostly helpful input, but here's a link you probably will find most directly helpful, from a brief series of articles on sorting from the 4 Guys from Rolla site, which back in the day was THE place for quality writing on ASP:
https://web.archive.org/web/20211020153403/https://www.4guysfromrolla.com/webtech/011601-1.shtml
You will see that there is a link to an introductory article at the top of this one that covers one-dimensional array sorting, and I recommend it as well. For one, it introduces another sort method, QuickSort, and having multiple tools in your toolbox is almost never a bad idea. (As you will discover, bubble sorting is often the easiest to envision and implement, but because its performance is essentially linear based on the number of items being sorted, can become a performance problem on larger datasets.) Go ahead, check it out; I'll wait 'til you get back...
2) OK, to give you a more concrete approach to address your specific situation here, if the number of files you're reviewing isn't going to be too large, you can do a sort of "final pass" sort to present your results in the desired order.
First, you'll want to introduce a simple global counting variable up toward the top of your code:
dim intMaxEntries
intMaxEntries = 0
Then, at the end of each of your file-parsing runs, you'll want to check the number of entries against intMaxEntries and update intMaxEntries if the number of entries just read in is greater.
if a(0, 1) > intMaxEntries then
intMaxEntries = a(0, 1)
end if
You'll do right after each file reading loop, so the comparison in the above snippet would be done for a(0, 1), a(1, 1), and a(2, 1). More on that repetitive logic at the end.
After you've done all the file reads, intMaxEntries will have the maximum number of entries you've found in one of the files. Then, you can just step down from that value and print out entry counts in the correct order when they match your countdown:
dim i, j
for i = intMaxEntries to 0 step -1
for j = 0 to ubound(a) 'By default gives the upper bound of the 1st dimen.
if a(j, 1) = i then
Response.Write i & a(j, 2) & "<BR>"
end if
next j
next i
This is more than a bit of a hack, and I would encourage you to opt instead for doing a proper sort of your array so that you have something more generalizably useful, but it will work to get you where you want to go, especially if the number of files -- or the maximum number of entries -- isn't too large. You could also clean up my example by introducing the possibility of breaking out of the loops when all the files are accounted for, but I'll let you figure out if that's necessary.
3) You may have just simplified the codebase to get the concept across more cleanly (for which I applaud you if true), but just in case, I would encourage you to look at ways to modularize your work by building your file reading functionality as a function that is simply called with the file and string comparison information needed. (Also, probably an artifact of your snipping, but the "lone", "mile" and "Payne" references in there don't make sense; assuming those are the FSOs you are instantiating and have just forgotten to change them to fileA, fileB and fileC.)
Hope that helps a bit,
Bret
#bret
Someone else came through.
Here's a code that worked perfectly.
Would this be an example of "bubble sort"?
for k=23 to 0 Step-1
for j=0 to k
if (a(0,j)<a(0,j+1)) then
t1=a(0,j+1)
t2=a(1,j+1)
a(0,j+1)=a(0,j)
a(1,j+1)=a(1,j)
a(0,j)=t1
a(1,j)=t2
end If
next
next
for i=0 to 24
if a(0,i)>0 then
response.write (a(0,i)&a(1,i)&"<br>")
end if
next
set objFSO = Server.CreateObject("Scripting.FileSystemObject")
set objFolder = objFSO.GetFolder(server.mappath("Files"))
set objfiles = objFolder.Files
Function filesearch(name)
set searchname = objFSO.OpenTextFile(server.mappath(filename),1, true)
do until searchname.AtEndOfStream
lineData = lcase(searchname.ReadLine())
if instr(lineData,s)>0 then
instances = instances + 1
end if
Loop
End Function
For Each objFile in objFolder.Files
filesearch(objFile)
Response.Write filename & "<br>" & instances & "<br>" & "<br>"
Next
Set objFolder = Nothing
Set objFSO = Nothing
There are a few rough edges but what really bothers me now is the sorting. Where do I keep the bubble sort code?
EDIT:
I've got it work perfect with the following code.
For Each objFile in objFolder.Files
filesearch(objFile)
i = i + 1
a(0,i) = instances
a(1,i) = filename
Next
I was also wondering if there's anyway I could also write the total number of instances. I was able to do it before with:
for i=0 to 43
entries=entries+a(0,i)
next
I cant seem to make it work now.
EDIT:
Works now with:
for i = 0 to n
entries = entries + a(0,i)
next
I'm tying to make something in VBA that will basically list all the files in one or more directories starting from a root folder. Long story short, I'm using filesystemobject to run through all of the folders and then getting all the files in those folders. Moving to the next folder, etc.
The problem I'm running into is that I need to spit out my data (onto a sheet) in the same folder sort order as one might find in Windows. I know this isn't a fixed concept per say, so here's a quick example, as it's displayed in Windows(for me):
Windows Sort Order:
FolderTest\000
FolderTest\0
FolderTest\0001
Not too surprisingly, when using FSO it returns the sub folders in a different (perhaps more logical) order:
FolderTest\0
FolderTest\000
FolderTest\0001
I was hoping someone might have an idea of what one could do to get this to be resorted as it's displaying in Windows. This is just an example obviously, the files could be named anything, but it certainly seems to behave a lot better with alpha characters in the name. I'm not necessarily married to using FSO, but I don't even know where else to look for an alternative. I know I could potentially resort these in an array, but I'm not sure what kind of wizardry would be required to make it sort in the "proper" order. For all I know, there's some method or something that makes this all better. Thanks in advance for any help!
To whoever it may end up helping, the following code looks like it's giving me the results I was looking for, converting a list of subfolders into the same sort orders you (probably) find in Windows Explorer. Feeding in Subfolders from a Filesystem object, it spits the results out in an array (fnames). The code... it's not pretty. I'll be the first to admit it. Don't judge me too harshly. Big thanks #Paddy (see above) for pointing me towards StrCmpLogicalW (http://msdn.microsoft.com/en-us/library/windows/desktop/bb759947(v=vs.85).aspx)
Private Declare PtrSafe Function StrCmpLogicalW Lib "shlwapi" _
(ByVal s1 As String, ByVal s2 As String) As Integer
Sub filefoldersortWindows()
Dim folder As String
Dim fnames() As String, buffer As String, content As String
folder = "Your Path"
Set fsol = CreateObject("Scripting.fileSystemObject")
Set fold = fsol.GetFolder(folder)
FoldCount = fold.SubFolders.Count
ReDim fnames(FoldCount)
cFcount = 0
For Each fld In fold.SubFolders
cFcount = cFcount + 1
Namer$ = fld.Name
fnames(cFcount) = StrConv(Namer, vbUnicode)
Next
For AName = 1 To FoldCount
For BName = (AName + 1) To FoldCount
If StrCmpLogicalW(fnames(AName), fnames(BName)) = 1 Then
buffer = fnames(BName)
fnames(BName) = fnames(AName)
fnames(AName) = buffer
End If
Next
Next
For i = 1 To FoldCount
fnames(i) = StrConv(fnames(i), vbFromUnicode)
If i > 1 Then
content = content & "," & fnames(i)
Else
content = fnames(i)
End If
Next
End Sub
Ahh, I see now. I made a bunch of directories with numeric names to see what's going on. Windows explorer does an integer conversion on the value. The sort rule is like this:
numeric value : ascending
padding length : descending
So, if have 01 and 001, both evaluate to the integer 1, but 001 will appear first because it is longer (has more zero-padding). The 'length' in this case only refers to the numeric part (ie the padding), and is not affected by any characters that appear after (they only matter if the numeric value and the padding length are the same - then normal ordering applies):
I have declared a two dimensional array in the function library and associated it with a test. In action1 of the test, I tried to clear the array using "erase" statement.
My code -
In Function Library,
Dim strVerifyAry(25,6)
In action1,
erase strVerifyAry
Error message
Run Error - Type mismatch: 'Erase'
How to clear the contents of this array?
Works for me in plain VBScript, so it's most likely an issue with whatever engine QTP uses for running VBScript code. You should be able to emulate the behavior of Erase for a 2-dimensional array like this:
Sub EraseArray(ByRef arr)
For i = 0 To UBound(arr, 1)
For j = 0 To UBound(arr, 2)
If IsObject(arr(i, j)) Then
Set arr(i, j) = Nothing
Else
arr(i, j) = Empty
End If
Next
Next
End Sub
Or like this, if you don't want to set fields containing objects to Nothing:
Sub EraseArray(ByRef arr)
For i = 0 To UBound(arr, 1)
For j = 0 To UBound(arr, 2)
arr(i, j) = Empty
Next
Next
End Sub
I do not exactly understand why, but you can create a sub like
Public Sub DoErase (byRef Ary)
Erase Ary
End Sub
in the library, and call it from within the action like this:
DoErase StrVerifyAry
and that works.
Update: No it doesn't. The array is successfully passed to DoErase, and the DoErase call works fine, but the test afterwards still can reference the array elements that Erase was supposed to be erasing.
If the test declares the array, it works fine (Erase erases the elements).
This is very strange and probably has to do with the quirky scopes in function libraries.
Please let us know if you ever find out what's going on here...
This drove me nuts for an entire afternoon so I wanted to post an answer for future reference. I filled an array using the Split command and then needed to Erase it before the script looped back through the process again. Nothing I tried would erase or clear the array and the next use of Split just appended to the previous array elements.
By trying the 'array=Nothing' loop above, I finally managed to generate a "This array is fixed or locked" error which I researched. Turns out I had used the array in a 'For Each..Next' loop which locks the array so it can't be erased or cleared. More info is available HERE:
You can use a Dictionary collection rather than an array in some circumstances. Then use RemoveAll when you want to clear it. That doesn't help when your array was created by a split function, or whatever, but it can help in other use cases.
Set myDict = CreateObject("Scripting.Dictionary")
...
myDict.RemoveAll
Refer to: https://www.w3schools.com/asp/asp_ref_dictionary.asp
I am writing a VBScript that automatically interacts with some web pages. I am having trouble at the final step where the script needs to click on a link to make a booking. The link for each time will only be available if that time is free. The idea of my code is to simply select the first time available (I originally though I could do this by using Mid() and GetElementId as I know the first 7 chars of each link ID but couldn't get this working). The array contains the IDs for all possible times available in a day. Some will already have been taken so that ID will no longer exist on the form.
I have 2 problems:-
1) Neither getElementBy Id or the Document.All.Item().Click commands will accept an element from the array - I get an Object Required run time error.
2) If getElementId doesn't find a matching ID it simply throws an Object required error. I wasn't expecting this, I thought that my elem variable would be nothing or null and that I could test for this.
Can anyone give me any pointers?
'This is a shortened version of my array- there are lots more times!
Times(0)="bookBtn0810"
Times(1)="bookBtn0818"
Times(2)="bookBtn0826"
Dim TimeAvail
Dim i
Dim elem
TimeAvail = "No"
i = 0
Do While (TimeAvail = "No") or (i<3)
Set elem = IE.Document.GetElementById(Chr(34) & Times(i) & Chr(34)) 'Chr(34) is to add ""
if elem is nothing then
TimeAvail = "No"
i=i+1
else
TimeAvail = "Yes"
IE.Document.All.Item(Chr(34) & Times(i) & Chr(34)).click
end if
Loop
Now, unless I'm being very silly, you won't be able to sit a variable to a non-existent element.
The only thing I can think of is to add:
On Error Resume Next
At the beginning, so it skips the error message. You may need to handle the error separately yourself.