How evaluate concatenated string as indexed XPath expression in VB6 - xpath

I've built an Xpath expression by concatenating strings in VB6:
strXPath = "xDOC.selectNodes(" & """/GroupType1""" & ").item(" & CStr(i) & ").selectNodes(" & """/OperationStageCollection/OperationStage""" & ").length"
"i" is an integer used to index into
I want to evaluate strXPath to get a loop counter, for example:
n = CInt(strXPath)
n is declared as Integer; strXPath is declared as string. VB6 throws a Type Mismatch error on the above evaluation expression. I must be missing something obvious. How can I evaluate strXPath?
I realize that there may be errors in the XPath expression itself, but I'd like to get the evaluation working in order to debug such possible errors.

Try removing some of the double-quotes:
iLength = xDOC.selectNodes("/GroupType1").item(i).selectNodes("/OperationStageCollection/OperationStage").length
This should return the length property you want, as an Integer.
Then you can use iLength in your loop.

#BRW: both of your questions are very specific, i.e. how to achieve certain results using XPath. But I have the suspicion that if you would explain what (data) you try to retrieve form the XML, commenters might show you ways you didn't think of, e.g. say you want to iterate through all <OperationEvent>s within a <OperationEventCollection>, a single <OperationEvent> can be retrieved by //GroupType1/OperationStageCollection/OperationStage/OperationEventCollection/OperationEvent[1-based-index], e.g. //GroupType1/OperationStageCollection/OperationStage/OperationEventCollection/OperationEvent[1], which results in a single XML node:
<OperationEvent>
<OperationEventDate1>2018-12-16</OperationEventDate1>
<OperationEventCode>5</OperationEventCode>
<OperationEventDate2>2018-05-16</OperationEventDate2>
</OperationEvent>
So instead multiple selectNodes methods, one proper XPath query might yield the desired outcome right away.

Related

Understanding the behaviour of Function.ScalarVector

I have a use case where I would like to pass two arguments to a function generated by Function.ScalarVector (see https://learn.microsoft.com/en-us/powerquery-m/function-scalarvector).
I would like the second argument to, optionally, capture two or more columns of a table, e.g. ScalarFun([Col1], {[Col2], [Col3]}). I would expect this to pass a list of lists to the function itself. Alas, it does not.
Consider this example, if I define a function to simply capture the generated input list (which we'd normally pass on to a function doing something useful with the list) we can see that passing a list of two values and a concatenation of two values generate very different behaviour:
let
ScalarFun =
Function.ScalarVector(
type function (col as any) as any,
(t) =>
let
buf = Table.Buffer(t)
in
List.Transform(buf[col], each List.Distinct(buf[col]))
),
TestTable = Table.FromColumns(
{{"a","b","c"}, {"x","y","z"}},
{"Col2", "Col3"}
),
#"List syntax" = Table.AddColumn(
TestTable,
"List1",
each Text.Combine(List.First(ScalarFun({[Col2],[Col3]})), ";")
),
#"Concactenation syntax" = Table.AddColumn(
#"List syntax",
"List2",
each Text.Combine(ScalarFun([Col2] & [Col3]), ";")
)
in
#"Concactenation syntax"
Given this output I can see that the syntax where we combine two values into a list ScalarFun([Col1], {[Col2], [Col3]}) actually operate row by row. Concatenating two text values however works just fine (the function gets passed a list containing the entire column).
A list is just a value like any other, so I don't understand why my preferred syntax doesn't work?
Update:
It's clear to me that the function does not behave in the way I anticipated due to lazy evaluation. I see the same issue if I try to use a record. Passing a list in this rather convoluted way 'works' however:
Text.Split(Text.Combine({[Col2],[Col3]}, ";"),";")
I'm not going to post this as an answer because I don't fully understand why this (silently, without an error) breaks the intended behaviour of Function.ScalarVector.
A list of lists is not the same as concatenated lists so I don't see any reason to expect them to behave the same way.
{TestTable[Col2], TestTable[Col3]} = {{a,b,c},{x,y,z}}
TestTable[Col2] & TestTable[Col3] = {a,b,c,x,y,z}

How to call Lua table value explicitly when using integer counter (i,j,k) in a for loop to make the table name/address?

I have to be honest that I don't quite understand Lua that well yet. I am trying to overwrite a local numeric value assigned to a set table address (is this the right term?).
The addresses are of the type:
project.models.stor1.inputs.T_in.default, project.models.stor2.inputs.T_in.default and so on with the stor number increasing.
I would like to do this in a for loop but cannot find the right expression to make the entire string be accepted by Lua as a table address (again, I hope this is the right term).
So far, I tried the following to concatenate the strings but without success in calling and then overwriting the value:
for k = 1,10,1 do
project.models.["stor"..k].inputs.T_in.default = 25
end
for k = 1,10,1 do
"project.models.stor"..j..".T_in.default" = 25
end
EDIT:
I think I found the solution as per https://www.lua.org/pil/2.5.html:
A common mistake for beginners is to confuse a.x with a[x]. The first form represents a["x"], that is, a table indexed by the string "x". The second form is a table indexed by the value of the variable x. See the difference:
for k = 1,10,1 do
project["models"]["stor"..k]["inputs"]["T_in"]["default"] = 25
end
You were almost close.
Lua supports this representation by providing a.name as syntactic sugar for a["name"].
Read more: https://www.lua.org/pil/2.5.html
You can use only one syntax in time.
Either tbl.key or tbl["key"].
The limitation of . is that you can only use constant strings in it (which are also valid variable names).
In square brackets [] you can evaluate runtime expressions.
Correct way to do it:
project.models["stor"..k].inputs.T_in.default = 25
The . in models.["stor"..k] is unnecessary and causes an error. The correct syntax is just models["stor"..k].

Issue w/ Symbol Substitution in Mathematica

I want to define a symbol and use it within a function. For example, with IDnumbers defined as a list of numbers:
ParallelMap[{#1, Name[#1], Age[#1]} &, IDnumbers]
With userlist={#1, Name[#1], Age[#1]} becomes:
ParallelMap[userlist &, IDnumbers]
It works just fine with the list itself in the code, but not with the symbol. The same thing happens with a list of strings vs. a symbol assigned to a list of strings. Why is this?
Since f[#]& is shorthand for Function[f[#]] you should always complete your anonymous function with a trailing & to get a working function.
In your example:
userlist={#1, Name[#1], Age[#1]}&
ParallelMap[userlist, IDnumbers]
More thorough explanation:
By just using something like f[#] you get (in FullForm[])
In[15] := f[#] // FullForm
Out[15]//FullForm = f[Slot[1]]
whereas this gets transformed to a Function by the trailing & operator:
In[16] := f[#]& // FullForm
Out[16]//FullForm = Function[f[Slot[1]]]
If you do this in two steps, & doesn't evaluate the intermediate variable expr:
In[25]:= expr = f[#]//FullForm
In[26]:= expr &
Out[25]//FullForm = f[Slot[1]]
Out[26] = expr &
You can force the evaluation of expr before it gets wrapped in the Function[] by using Evaluate[]:
In[27]:= expr=f[#]//FullForm
In[28]:= Evaluate[expr]&
Out[27]//FullForm = f[Slot[1]]
Out[28] = f[Slot[1]]&
Another way is to supply the Function[] wrapper yourself:
userlist={#1, Name[#1], Age[#1]}
ParallelMap[Function[userlist], IDnumbers]
Personally, i would consider this bad coding style. Just get used to always finishing an anonymous function with a trailing & like you would supply a closing paranthesis ) to a corresponding opening one (.
Edit
Ok, in your case of a dynamically generated anonymous function i can see why you couldn't supply the & directly. Just wrap the expression with the Slot[]s in a Function[] instead.

VBScript: Finding the number of non-null elements in an array

What is the "best" way to determine the number of elements in an array in VBScript?
UBound() tells you how many slots have been allocated for the array, but not how many are filled--depending on the situation, those may or may not be the same numbers.
First off there is no predefined identifier called vbUndefined as the currently accepted answer appears to imply. That code only works when there is not an Option Explicit at the top of the script. If you are not yet using Option Explicit then start doing so, it will save you all manner of grief.
The value you could use in place of vbUndefined is Empty, e.g.,:-
If arr(x) = Empty Then ...
Empty is a predefined identify and is the default value of a variable or array element that has not yet had a value assigned to it.
However there is Gotcha to watch out for. The following statements all display true:-
MsgBox 0 = Empty
MsgBox "" = Empty
MsgBox CDate("30 Dec 1899") = True
Hence if you expect any of these values to be a valid defined value of an array element then comparing to Empty doesn't cut it.
If you really want to be sure that the element is truely "undefined" that is "empty" use the IsEmpty function:-
If IsEmpty(arr(x)) Then
IsEmpty will only return true if the parameter it actually properly Empty.
There is also another issue, Null is a possible value that can be held in an array or variable. However:-
MsgBox Null = Empty
Is a runtime error, "invalid use of null" and :-
MsgBox IsEmpty(Null)
is false. So you need to decide if Null means undefined or is a valid value. If Null also means undefined you need your If statement to look like:-
If IsEmpty(arr(x)) Or IsNull(arr(x)) Then ....
You might be able to waive this if you know a Null will never be assigned into the array.
I'm not aware of a non-iterative method to do this, so here's the iterative method:
Function countEmptySlots(arr)
Dim x, c
c = 0
For x = 0 To ubound(arr)
If arr(x) = vbUndefined Then c = c + 1
Next
countEmptySlots = c
End Function
As Spencer Ruport says, it's probably better to keep track yourself to begin with.
There's nothing built in to tell you what elements are filled. The best way is to keep track of this yourself using a count variable as you add/remove elements from the array.

Is it better to use NOT or <> when comparing values?

Is it better to use NOT or to use <> when comparing values in VBScript?
is this:
If NOT value1 = value2 Then
or this:
If value1 <> value2 Then
better?
EDIT:
Here is my counterargument.
When looking to logically negate a Boolean value you would use the NOT operator, so this is correct:
If NOT boolValue1 Then
and when a comparison is made in the case of the first example a Boolean value is returned. either the values are equal True, or they are not False. So using the NOT operator would be appropriate, because you are logically negating a Boolean value.
For readability placing the comparison in parenthesis would probably help.
The latter (<>), because the meaning of the former isn't clear unless you have a perfect understanding of the order of operations as it applies to the Not and = operators: a subtlety which is easy to miss.
Because "not ... =" is two operations and "<>" is only one, it is faster to use "<>".Here is a quick experiment to prove it:
StartTime = Timer
For x = 1 to 100000000
If 4 <> 3 Then
End if
Next
WScript.echo Timer-StartTime
StartTime = Timer
For x = 1 to 100000000
If Not (4 = 3) Then
End if
Next
WScript.echo Timer-StartTime
The results I get on my machine:
4.783203
5.552734
Agreed, code readability is very important for others, but more importantly yourself. Imagine how difficult it would be to understand the first example in comparison to the second.
If code takes more than a few seconds to read (understand), perhaps there is a better way to write it. In this case, the second way.
The second example would be the one to go with, not just for readability, but because of the fact that in the first example, If NOT value1 would return a boolean value to be compared against value2. IOW, you need to rewrite that example as
If NOT (value1 = value2)
which just makes the use of the NOT keyword pointless.

Resources