vbscript Execute: exit external For - vbscript

Execute in vbscript is used to parse strings into inline instructions. However, I can't use it to break For loops. The following won't work:
For i = 1 To 10
msgbox i
Execute("Exit For")
Next
Does somebody know any workaround for it to work? Or any way for Exit Sub/Exit Function work? I tried ExecuteGlobal, but it also raises "invalid exit instruction" error.
I used it as a form of "import" throughout my code and it is going to be time consuming to implement another form of import all over again, so I am searching for a way to make it work before considering to change the project.
EDIT: more examples
In short, I tried to do a partial override of a semi-abstract method, but can't use Exit For against a For outside the Execute block.
Dim names_array 'this array will be populated in another Sub
'this method will loop though all elements of an array and insert some items into a database...
'but the method does not know (how to)/(if it will really) insert into the database!
'all it knows is: it has to loop though the array... and, if needed, finish earlier
'the insertion part will have to be described before running, like a 'partial override' of an abstract method
'that is because I know how part of the method must be, but the details will be diferent for each database
'I don't want to override every method entirely!
Sub insert_into_database
For i = 0 To UBound(names_array)
name = names_array(i)
Execute(core_op) 'here comes the core operation, it is what I override!
Next
End Sub
'for some databases, core_op will be simple:
core_op = "objRecordSet.Open ""INSERT INTO videos (title, date_time) VALUES ('"" & name & ""', Now)"", objConnection"
'for others, it may be complex:
core_op = "If name=""18+ only"" Then"
core_op = core_op + " MsgBox ""You tried to insert nasty things. System will now halt."""
core_op = core_op + " Exit For"
core_op = core_op + "Else"
core_op = core_op + " objRecordSet.Open ""INSERT INTO videos (title, date_time) VALUES ('"" & name & ""', Now)"", objConnection"
core_op = core_op + "End If"
'the string won't be loaded this way, it will come from an external file
'there are other methods that operate like this, but not all of them need Exit For
That is closer to my project. The reality is: the code for the overrides will come from an external file and will be stored in a dictionary.
Sorry if it seems a mess, that is the way I tried to make vbscript more java-like regarding reusability.

Exit statements are parts of compound statements (loops, routines) and must be 'compiled' in their contexts. So there is no workaround and you have to redesign your import strategy. Perhaps you should publish your current approach.
Update wrt comment (partial override of the method):
Use GetRef and pass "function pointers", or invest in some classes and send objects to the enclosing function.

Related

How can I get the position of a CAD part via VBscript?

I want to write a VBScript, which outputs the position of a CAD part (x,y,z). The Input should be the partnumber. This is what I tried.
Sub CATMain()
dim pos(11)
for n = 1 to CATIA.Documents.Count
set Dokument = CATIA.Documents.Item(n)
for i = 1 to Dokument.product.products.Count
set InstDokument = Dokument.product.products.item(i)
If InstDokument = "my_part" Then
msgbox InstDokument.Name
InstDokument.Position.GetComponents pos
msgbox "Origin Point: X= " &pos(9) &" Y= " &pos(10) &" Z= " &pos(11)
End If
next
next
End Sub
I got an error in line 8 column 2. The object does not have the property or method.: Dokument.product
How can I solve that?
There are several problems with your code.
At the root is probably this:
set Dokument = CATIA.Documents.Item(n)
The CATIA documents collection will contain many documents which do not have windows and which the CATIA application maintains for it's own various internal purposes. So it is not assured that CATIA.Documents.Item(n) actually contains a CATProduct.
In most cases one is interested in the current active document, and it is retrieved like this:
Set Dokument = CATIA.ActiveDocument
Otherwise you can test for it
Set Dokument = CATIA.Documents.Item(n)
if typename(Dokument) = "ProductDocument" Then ...
Even after that you have problems. You are comparing the Document object to a string... and other things. Also without recursion you may never find your target instance if it is deeper then at the first level. It may also be possible that search may be a better way to find your instance then "Reading the tree".
Once you have corrected all your infrastructure errors your method for getting the transformation matrix is basically correct.

VBA loop efficiency

I've used the answers found in the site a TON of times, but this is my first post.
Is it more efficient to perform a for loop inside a function or sub or to just send the incremented value of the loop into the function or sub as an argument?
I am importing some text data files into Excel, parsing out the various fields, and then splitting some of the fields into individual characters. One file I am using is a list of doctors. I have name, address, phone, DEA number, NPI, etc.
When checking the DEA number, I have a sub that receives the line number to be checked that splits the DEA into its individual digits, perform checking on these digits one at a time and then modify another field with the status of that DEA. This status cell will be colored red if it contains anything but the word "GOOD". Also, I am coloring the individual digit that is bad, if applicable.
This one sub is doing a lot and I could probably break it up a little, but there aren't any other places in the doctor file that I am performing this exact step, so I figured I should keep it like it is.
Anyways, the real question is whether I should send the line number into the sub or should I just call the sub and have the sub calculate the number of lines and do the checking. In the first case, I will call the sub a number of times equal to the number of lines in the doctor file. In the second, I will call the sub once and the sub contains the for loop for each line. Which is usually more efficient.
Apologies if I seem redundant. I train some complex software and that sort of thing leaks into other areas of life sometimes.
EDIT: I tried to add this into a comment but have insufficient experience posting here. Apologies if I violate some rule for this...
Here is the code I use currently to call the sub:
'Use the Doctor Last Name as the number of rows count
Dim numRows As Integer
numRows = Application.CountA(Sheets("DoctorDEA").Range("m:m"))
'lineCtr is the Line Counter used to iterate the FOR loops
Dim lineCtr As Integer
lineCtr = 1
'Call DEACHecking and DisplayIssues Subs
For lineCtr = 1 To numRows - 1
DEAChecking (lineCtr)
DisplayIssues (lineCtr)
Next lineCtr
My questions is this: Would it be better to just call DEAChecking with no arguments and just have DEAChecking calculate the line numbers and then use the FOR loop or to leave it as is?
This question is too broad right now to be answered effectively. So am just offering a small insight that might help you structure your program.
Typically the most efficient code is the one where all the variables are as local as possible. If inside a loop you are using globals, or calling other functions it is going to be much worse than performing all the calculation with local variables.
If you want to test each, and time them, you can use a timer. If you have a major gap, you will be able to catch it. If not, you will have your answer with no significant difference as far as processing time.
You can either use this and call your sub from TimerTest, or simply Call TimerStart at the beginning of your code and TimerStop at the end.
Run some code with the timer
Log the result
Repeat and compare
HH:MM:SS:00 format
Timer Code:
Public strStartTime As String
Public strEndTime As String
Public startTime As Date
Public endTime As Date
Sub timeTest()
Call TimerStart
'INSERT CALL TO YOUR SUB HERE
Call TimerStop
End Sub
Sub TimerStart()
startTime = Now
End Sub
Sub TimerStop()
endTime = Now
'Waited until the timer stopped to perform any additional code, such as formatting the time
Dim TotalTime As String
strStartTime = Format(startTime, "hh:mm:ss:" & Right(Format(Timer, "#0.00"), 2))
strEndTime = Format(endTime, "hh:mm:ss:" & Right(Format(Timer, "#0.00"), 2))
TotalTime = Format(endTime - startTime, "hh:mm:ss:" & Right(Format(Timer, "#0.00"), 2))
MsgBox (" Start: " & strStartTime & vbNewLine & _
" End: " & strEndTime & vbNewLine & _
"Total Time : " & TotalTime)
End Sub
Credit: #Nick Dandoulakis for timer formatting in his answer here: Providing this solution to show clock time with accuracy of less than a second.

Is there a better way to write the following VB6 snippet?

I work at $COMPANY and I'm helping maintain $LEGACY_APPLICATION. It's written in visual basic 6.
I was faced with doing an unpleasantly elaborate nested if statement due to the lack of VB6's ability to perform short circuit evaluations in if statements (which would simplify this a lot). I've tried AndAlso, but to no avail. Must be a feature added after VB6.
Some genius on SO somewhere pointed out that you can trick a select case statement into working like a short-circuiting if statement if you have the patience, so I tried that, and here's what I came up with:
Select Case (True) ' pretend this is an if-else statement
Case (item Is Nothing): Exit Sub ' we got a non-element
Case ((item Is Not Nothing) And (lastSelected Is Nothing)): Set lastSelected = item ' we got our first good element
Case (item = lastSelected): Exit Sub ' we already had what we got
Case (Not item = lastSelected): Set lastSelected = item ' we got something new
End Select
It's definitely a little unusual, and I had to make use of my fantastic whiteboard (which, by the way, is pretty much the most useful programming resource besides a computer) to make sure I had mapped all of the statements correctly.
Here's what's going on there: I have an expensive operation which I would like to avoid repeating if possible. lastSelected is a persistent reference to the value most recently passed to this calculation. item is the parameter that was just received from the GUI. If there has never been a call to the program before, lastSelected starts out as Nothing. item can be Nothing too. Additionally, if both lastSelected and item are the same something, skip the calculation.
If I were writing this in C++, I would write:
if (item == NULL || (lastSelected != NULL && item->operator==(*lastSelected))) return;
else lastSelected = item;
However, I'm not.
Question
How can I rewrite this to look better and make more sense? Upvotes will be awarded to answers that say either "YES and here's why: X, Y, Z" or "NO, and here's why not: X, Y, Z".
Edits
Fixed the C++ statement to match the VB6 one (they were supposed to be equivalent)
This is shorter and 100x more readable.
EDIT Wug edited the code in MarkJ's original answer, into this:
If (item Is Nothing)
Then Exit Sub ' we got a non-element
ElseIf (lastSelected Is Nothing) Then
Set lastSelected = item ' we got our first go
ElseIf (item = lastSelected) Then
Exit Sub ' we already had what we got
End If
Set lastSelected = item ' we got something new
Here's MarkJ's edit in response. One nested if, but only one Set. Seems neater to me.
If (item Is Nothing) Then
Exit Sub ' we got a non-element
ElseIf Not (lastSelected Is Nothing) Then ' not our first go
If (item = lastSelected) Then
Exit Sub ' we already had what we got
End If
End If
Set lastSelected = item ' we got something new
' does stuff here? #Wug is that true?
To compare reference equality in VB6 use item Is LastSelected. Because item = lastSelected will probably evaluate the default properties in the objects and compare those instead!
Since brevity appears to be a goal, consider this. If you Exit Sub when condition X is True, you don't need to check X again later. It is False! Unless it changes its value in between evaluations (e.g. X is a function that checks the system clock). You were checking whether item was lastSelected, then whether it wasn't. And if item Is Nothing is False, do not bother to check whether item Is Not Nothing is True!
VB6 does not short circuit for backwards compatibility with ancient versions of Basic
Stop worrying that VB6 is not some other language and relax!
YES
I translated it from your case statement. I find it easier to read, personally.
If Item Is Nothing Then
Exit Sub ' we got a non-element
ElseIf LastSelected Is Nothing Then
Set LastSelected = Item ' we got our first good element
ElseIf Item = LastSelectedItem Then
Exit Sub ' we already had what we got
Else
Set LastSelected = Item ' we got something new
End If
You asked for explanation. I tried not to have to give much (by re-using your own code comments).
But here it is anyway :-)
Firstly if there is no item, just exit. Easy.
Otherwise if LastSelected Is Nothing then we know, because the first if condition failed, that Item exists, and it's safe to mark that value as having been Last Selected. As you say, we got our first good element. The sub continues on.
However if we have existing values for Item and LastSelected, then either they are equal or not. If they are equal, just quit.
If they aren't equal, then update LastSelected. As you say, we got something new.
You can use a helper function like this:
Private Function pvGetItemData(oItem As ListItem) As Variant
If Not oItem Is Nothing Then
pvGetItemData = oItem.Tag
Else
pvGetItemData = -1
End If
End Function
and then
If pvGetItemData(Item) = pvGetItemData(LastSelected) Then
' cache hit
Else
' do calc
Set LastSelected = Item
End If
YES
I'd make that simpler:
If item Is Nothing Then
Exit Sub ' we got a non-element
Else
Set lastSelected = item ' we got something to assign
End If
Unless there are side effects assigning lastItem (it can be property with invalid assignment code), then code logic is essentially same.
If you are not required to exit the sub (snippet is at the end of sub or something), then next is even simpler:
If Not (item Is Nothing) Then Set lastSelected = item
BTW, your Select Case (True) looks really odd to VB programmer :)

VBScript - Putting Array Element into GetElementById

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.

how to treat empty parameters in function calls?

When you call a function like x = myfunc(a,b,,d) what happens with the 3rd parameter? is it empty? null? nothing?
I'm having issues with my function, say,
function myfunc(p1, p2, p3, p4)
if p3 <> "" then whatever
end function
gives me a dreaded type mismatch
P.S. I'm trying to replace a COM object with vbscript functions, and those empty parameter calls were done to the COM objects which didn't have a problem with those, but vbscript doesn't like them. I can't change the calls, only the functions so I need to treat the empty parameter somehow, just don't know how (tried isnull with no luck, also isempty no luck, and is nothing gives me an object required error)
The data type of missed parameters is ERROR.
I wrote an example, try the following:
Function IsMissing(p)
IsMissing = (VarType(p) = vbError)
End Function
Function Myfunc(p1, p2, p3, p4)
If IsMissing(p1) Then Response.Write "p1 is missing "
If IsMissing(p2) Then Response.Write "p2 is missing "
If IsMissing(p3) Then Response.Write "p3 is missing "
If IsMissing(p4) Then Response.Write "p4 is missing "
End Function
str = myfunc(, , , 1)
'str = myfunc(1, 2, , ) 'causes error
Notice: The last parameter of function / sub cannot be blank because of syntax error.
In the spirit of teaching a man to fish:
The TypeName function will reveal to you what type it is.
MsgBox TypeName(p3) ' Returns string: "Error"
Instead of if p3 <> "" you can test for omitted arguments like this:
If TypeName(p3) = "Error" Then MsgBox "Whatever!"
or, preferably,
If VarType(p) = vbError Then MsgBox "Whatever!"
' vbError is a built-in constant whose value is 10.
But really, for best practice, your program design should avoid omitting arguments. VBScript isn't really made to deal with "optional" arguments.
VBScript doesn't support optional parameters. They can be simulated by passing an array or null values in, which isn't helpful in your situation since you can not change the calls. The next solution might be to consider changing the platform (may I suggest .Net) or an On Error Resume Next hack like the one below.
'Assuming a String Parameter
Dim strOptionalParameter As String
strOptionalParameter = p3
'We set the value of the optional parameter to a variable
'in case it blows up and moves on to this next statement.
If strOptionalParameter <> "" Then
'Rest of Code Here
End If

Resources