Classic ASP - passing a property as byref - properties

In Classic ASP, I have an object, call it bob. This then has a property called name, with let and get methods.
I have a function as follows:
sub append(byref a, b)
a = a & b
end sub
This is simply to make it quicker to add text to a variable. I also have the same for prepend, just it is a = b & a. I know it would be simple to say bob.name = bob.name & "andy", but I tried using the above functions and neither of them work.
The way I am calling it is append bob.name, "andy". Can anyone see what is wrong with this?

Unfortunately this is a feature of VBScript. It is documented in http://msdn.microsoft.com/en-us/library/ee478101(v=vs.84).aspx under "Argument in a class". The alternative is to use a function. Here is an example illustrating the difference. You can run this from the command line using "cscript filename.vbs.
sub append (a, b)
a = a & b
end sub
function Appendix(a, b)
Appendix = a & b
end function
class ClsAA
dim m_b
dim m_a
end class
dim x(20)
a = "alpha"
b = "beta"
wscript.echo "variable works in both cases"
append a, b
wscript.echo "sub " & a
a = appendix(a, b)
wscript.echo "function " & a
x(10) = "delta"
wscript.echo "array works in both cases"
append x(10), b
wscript.echo "sub " & x(10)
x(10) = appendix( x(10), b)
wscript.echo "function " & x(10)
set objAA = new ClsAA
objAA.m_a = "gamma"
wscript.echo "Member only works in a function"
append objAA.m_a, b
wscript.echo "sub " & objAA.m_a
objAA.m_a = appendix(objAA.m_a, b)
wscript.echo "function " & objAA.m_a

Have you tried using with the keyword CALL:
call append (bob.name, "andy")
Classic ASP is fickel about ByRef and ByVal. By default it uses ByRef -- no reason to specify that. If you call a function with parenthesis (without the call), it will pass the variables as ByVal.
Alternatively, you could accomplish the same with:
function append(byref a, b)
append = a & b
end sub
bob.name = append(bob.name, "andy");
Good luck.

As this other answer correctly states, you are facing limitation of the language itself.
The only other option to achieve what you are after as far as I can see it, is to add such sub routine to the class itself:
Public Sub Append(propName, strValue)
Dim curValue, newValue
curValue = Eval("Me." & propName)
newValue = curValue & strValue
Execute("Me." & propName & " = """ & Replace(newValue, """", """""") & """")
End Sub
Then to use it:
bob.Append "name", "andy"
Less elegant, but working.

Related

Modify matrix column spacing in equation through Word VBA

I would like to automate vertically stacked math problems (sums, products, etc.).
By using matrices I can align the numbers to the right so the digits align.
However, the column spacing default is too wide:
I can manually right click the matrix, select matrix spacing and set the minimum distance between columns to exactly 1, achieving my goal:
I cannot get the syntax of the matrix manipulation in VBA. The documentation seems very sparse (no examples). I tried recording a macro, but the right-click menu does not appear for the matrix in the equation when recording. I am not sure how to "set" the OMathMat object, since it is not a property of OMath.
I would settle for code that looped through all the equation objects in the document, all the matrix objects in those equations, and updated the OMathMat.ColSpacing property.
I tried something like:
For Each equation In ActiveDocument.OMaths
For Each Func In equation.Functions
Func.Mat.ColSpacing = 1
Next
Next
But the requested member (Mat) of the collection (Functions) did not exist. Also, there seems to be OMathFunction.Mat and OMathMat. I think I need the second option.
I agree that there isn't an obvious place to find documentation about accessing the OMath objects, so started trying to put something together.
But then you answered your own question - at this point it's probably more useful to publish what I already have, despite the fact that there are many unanswered questions.
So here are a few pieces of code that may help shed some light. They aren't well-tested.
I may well try to improve this question in future, but it will take time.
The first set of code should be able to deal with the situation where your Matrix objects could be anywhere in an OMath object.
The second set of code implements an "explorer" that reports the structure of the OMath objects in the main body of the document via Debug.Print statements.
Then there are a few more notes at the end.
Here's the code that should do what you needed, but slightly more generalised. You can copy the code into a single Module and run it.
' Keep some running totals
Dim OMathCount As Integer
Dim FunctionCount As Long
Dim MatCount As Long
Sub processOMaths()
Dim i As Long
FunctionCount = 0
MatCount = 0
' Just process the document body
With ActiveDocument
For i = 1 To .OMaths.Count
With .OMaths(i)
Call processOMathFunctions(.Functions)
End With
OMathCount = i
Next
End With
MsgBox "Processed " & CStr(OMathCount) & " Equation(s), " & _
CStr(FunctionCount) & " Function(s), " & _
CStr(MatCount) & " Matrix object(s)"
End Sub
Sub processOMathFunctions(oFunctions As OMathFunctions)
' There does not seem to be a way to return the entire collection of Functions
' in an OMath object. So it looks as if we have to recurse. But because the
' Object names for different Functions are different, we can't easily drill down
' to the next level using exactly the same code for multiple object types...
Dim i As Integer
For i = 1 To oFunctions.Count
Call processSingleOMathFunction(oFunctions, i)
Next
End Sub
Sub processSingleOMathFunction(oFunctions As OMathFunctions, index As Integer)
' ...so unless someone has a better idea, we'll just use a Select Case
' statement and deal with all the possible Function types
FunctionCount = FunctionCount + 1
With oFunctions(index)
Select Case .Type
Case WdOMathFunctionType.wdOMathFunctionAcc
Call processOMathFunctions(.Acc.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionBar
Call processOMathFunctions(.Bar.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionBorderBox
Call processOMathFunctions(.BorderBox.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionBox
Call processOMathFunctions(.Box.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionDelim
Dim delimCount As Integer
For delimCount = 1 To .Delim.E.Count
Call processOMathFunctions(.Delim.E(1).Functions)
Next
Case WdOMathFunctionType.wdOMathFunctionEqArray
Dim eqCount As Integer
For eqCount = 1 To .EqArray.E.Count
Call processOMathFunctions(.EqArray.E(eqCount).Functions)
Next
Case WdOMathFunctionType.wdOMathFunctionFrac
Call processOMathFunctions(.Frac.Num.Functions)
Call processOMathFunctions(.Frac.Den.Functions)
Case WdOMathFunctionType.wdOMathFunctionFunc
Call processOMathFunctions(.Func.E.Functions)
Call processOMathFunctions(.Func.FName.Functions)
Case WdOMathFunctionType.wdOMathFunctionGroupChar
Call processOMathFunctions(.GroupChar.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionLimLow
Call processOMathFunctions(.LimLow.E.Functions)
Call processOMathFunctions(.LimLow.Lim.Functions)
Case WdOMathFunctionType.wdOMathFunctionLimUpp
Call processOMathFunctions(.LimUpp.E.Functions)
Call processOMathFunctions(.LimUpp.Lim.Functions)
Case WdOMathFunctionType.wdOMathFunctionLiteralText
' as far as I know, this cannot contain further Functions
' Do nothing.
Case WdOMathFunctionType.wdOMathFunctionMat
MatCount = MatCount + 1
Dim i As Integer
.Mat.ColGapRule = wdOMathSpacingExactly
' Hardcode this bit
.Mat.ColGap = 1 ' I think these are Twips, i.e. 1/20 pt
' We could iterate the columns and rows, but
' we'll iterate the Args instead.
For i = 1 To .Args.Count
Call processOMathFunctions(.Args(i).Functions)
Next
Case WdOMathFunctionType.wdOMathFunctionNary
Call processOMathFunctions(.Nary.Sub.Functions)
Call processOMathFunctions(.Nary.Sup.Functions)
Call processOMathFunctions(.Nary.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionNormalText
' Used for 'Non-Math text'
' Do nothing
Case WdOMathFunctionType.wdOMathFunctionPhantom
Call processOMathFunctions(.Phantom.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionRad
Call processOMathFunctions(.Rad.Deg.Functions)
Call processOMathFunctions(.Rad.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionScrPre
Call processOMathFunctions(.ScrPre.Sub.Functions)
Call processOMathFunctions(.ScrPre.Sup.Functions)
Call processOMathFunctions(.ScrPre.E.Functions)
Case WdOMathFunctionType.wdOMathFunctionScrSub
Call processOMathFunctions(.ScrSub.E.Functions)
Call processOMathFunctions(.ScrSub.Sub.Functions)
Case WdOMathFunctionType.wdOMathFunctionScrSubSup
Call processOMathFunctions(.ScrSubSup.E.Functions)
Call processOMathFunctions(.ScrSubSup.Sub.Functions)
Call processOMathFunctions(.ScrSubSup.Sup.Functions)
Case WdOMathFunctionType.wdOMathFunctionScrSup
Call processOMathFunctions(.ScrSup.E.Functions)
Call processOMathFunctions(.ScrSup.Sup.Functions)
Case WdOMathFunctionType.wdOMathFunctionText
' Text - do nothing
Case Else
MsgBox "OMath Function type " & CStr(.Type) & " not recognized. Ignoring."
End Select
End With
End Sub
The second lot of code is the Explorer. It's incomplete, in various ways. All the code could go in a single Module but as it stands it is divided into three Modules:
One module contains the main Explorer code, which is structured in a similar way to the code I posted above. I haven't completed the code for all the function types so you will see some TBD (To Be Done) comments.
' indentation increment for each level of oMath object nesting
Const incindent As String = " "
Sub exploremath()
' This code explores the structure of 'modern' equations in Word
' i.e. the sort that have neen in Word since around Word 2007, not the older
' types inserted using an ActiveX object or an EQ field.
' Note to English speakers: some places use "Math" to refer to Mathematics
' e.g. the US. Others, e.g. the UK, use "Maths". This can cause a bit of confusion
' for UK English speakers but the trick is to realise that the oMaths object
' is just a collection of oMath objects. i.e. the naming convention is exactly the same as
' e.g. Paragraphs/Paragraph and so on.
' The overview is that
' - each Equation is represented by an OMath object
' - an oMath object contains an oMathFunctions collection
' with 0 (?1) or more oMathFunction objects
' - an oMathFunction object can represent several different
' types of structure, not just those with familiar function names
' such as Sin, Cos etc. but structures such as Matrices,
' Equation Arrays and so on.
Dim eqn As oMath
Dim fn As OMathFunction
Dim i As Long
Dim j As Long
Dim indent As String
With ActiveDocument
For i = 1 To .OMaths.Count
With .OMaths(i)
Debug.Print "Equation " & CStr(i) & ":-"
indent = ""
Call documentOMathFunctions(.Functions, indent)
End With
Debug.Print
Next
End With
End Sub
Sub documentOMathFunctions(fns As OMathFunctions, currentindent As String)
Dim i As Integer
Dim indent As String
indent = currentindent & incindent
Debug.Print indent & "Function count: " & CStr(fns.Count)
For i = 1 To fns.Count
Call documentOMathFunction(fns, i, indent)
Debug.Print
Next
End Sub
Sub documentOMathFunction(fns As OMathFunctions, index As Integer, currentindent As String)
Dim indent As String
indent = currentindent & incindent
With fns(index)
Debug.Print indent & "Function " & CStr(index) & ", Type: " & OMathFunctionTypeName(.Type) & " :-"
Select Case .Type
Case WdOMathFunctionType.wdOMathFunctionAcc
' Accented object
Debug.Print indent & "Accent: " & debugPrintString(ChrW(.Acc.Char))
Call documentOMathFunctions(.Acc.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionBar
' object with an overbar
Debug.Print indent & "Bar " & AB(.Bar.BarTop) & ":-"
Call documentOMathFunctions(.Bar.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionBorderBox
' TBD
Case WdOMathFunctionType.wdOMathFunctionBox
Debug.Print indent & "Box: IsDifferential? " & YN(.Box.Diff) & _
", Breaks Allowed? " & YN(Not .Box.NoBreak) & _
", TreatAsSingleOp? " & YN(.Box.OpEmu)
Call documentOMathFunctions(.Box.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionDelim
' Brackets etc.
Debug.Print indent & "Delim: BeginningChar: " & _
debugPrintString(ChrW(.Delim.BegChar)) & _
", EndChar: " & debugPrintString(ChrW(.Delim.EndChar)) & _
", SeparatorChar: " & debugPrintString(ChrW(.Delim.SepChar))
Debug.Print indent & incindent & "Grow? " & _
YN(.Delim.Grow) & ", LeftChar Hidden? " & _
YN(.Delim.NoLeftChar) & ", RightChar Hidden? " & _
YN(.Delim.NoRightChar) & ", Appearance: " & OMathShapeTypeName(.Delim.Shape)
Dim delimCount As Integer
For delimCount = 1 To .Delim.E.Count
Debug.Print indent & "Part " & CStr(delimCount) & ":-"
Call documentOMathFunctions(.Delim.E(1).Functions, indent)
Next
Case WdOMathFunctionType.wdOMathFunctionEqArray
' Array of aligned equations
Debug.Print indent & "Equation Array: Vertical Alignment : " & OMathVertAlignTypeName(.EqArray.Align) & _
", Expand to page column width? " & YN(.EqArray.MaxDist)
Debug.Print "Expand to object width? " & YN(.EqArray.ObjDist) & _
", Row Spacing Rule: " & oMathSpacingRuleName(.EqArray.RowSpacingRule);
If .EqArray.RowSpacingRule = WdOMathSpacingRule.wdOMathSpacingExactly Then
Debug.Print ", Row Spacing: " & CStr(.EqArray.RowSpacing) & " twips"
ElseIf .EqArray.RowSpacingRule = WdOMathSpacingRule.wdOMathSpacingMultiple Then
' Don't know what the .rowspacing Unit is in this case
Debug.Print ", Row Spacing: " & CStr(.EqArray.RowSpacing) & " half-lines";
End If
Debug.Print
Dim eqCount As Integer
For eqCount = 1 To .EqArray.E.Count
Debug.Print indent & "Equation " & CStr(eqCount) & ":-"
Call documentOMathFunctions(.EqArray.E(eqCount).Functions, indent)
Next
Case WdOMathFunctionType.wdOMathFunctionFrac
' Fraction
Debug.Print indent & "Fraction numerator:-"
Call documentOMathFunctions(.Frac.Num.Functions, indent)
Debug.Print indent & "Fraction denominator:-"
Call documentOMathFunctions(.Frac.Den.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionFunc
' Function (not sure yet whether a 'Func' can
' only have a single argument (possibly blank))
Debug.Print indent & "Func name: " & debugPrintString(.Func.FName.Range.Text)
Call documentOMathFunctions(.Func.E.Functions, indent)
Call documentOMathFunctions(.Func.FName.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionGroupChar
' A character such as a brace over or under another Function.
Debug.Print indent & "Group Char: " & UHex(.GroupChar.Char) & ", Position: " & AB(.GroupChar.CharTop); ""
Call documentOMathFunctions(.GroupChar.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionLimLow
' A Limit with the small text under the 'Lim word'
Debug.Print indent & "'LimLow':-"
Debug.Print indent & "Base:-"
Call documentOMathFunctions(.LimLow.E.Functions, indent)
Debug.Print indent & "Lim:-"
Call documentOMathFunctions(.LimLow.Lim.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionLimUpp
' A limit with the small text over the 'Lim word'
Debug.Print indent & "'LimUpp':-"
Debug.Print indent & "Base:-"
Call documentOMathFunctions(.LimUpp.E.Functions, indent)
Debug.Print indent & "Lim:-"
Call documentOMathFunctions(.LimUpp.Lim.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionLiteralText
' 'Literal Text' at first sight seems to be followed by
' a wdOMathFunctionText function with a Range
' containing the actual text.
' To be explored further
' But for now, do nothing.
Case WdOMathFunctionType.wdOMathFunctionMat
' A Matrix. AFAIK they have to be rectangular
Dim i As Integer
Debug.Print indent & ", Column count: " & CStr(.Mat.Cols.Count) & _
", Column gap rule: " & oMathSpacingRuleName(.Mat.ColGapRule);
If .Mat.ColGapRule = WdOMathSpacingRule.wdOMathSpacingExactly Then
Debug.Print ", Spacing: " & CStr(.Mat.ColGap) & " twips";
ElseIf .Mat.ColGapRule = WdOMathSpacingRule.wdOMathSpacingMultiple Then
Debug.Print ", Spacing: " & CStr(.Mat.ColGap);
End If
Debug.Print
Debug.Print indent & "Row count: " & CStr(.Mat.Rows.Count) & _
", Row gap rule: " & oMathSpacingRuleName(.Mat.RowSpacingRule);
If .Mat.RowSpacingRule = WdOMathSpacingRule.wdOMathSpacingExactly Then
Debug.Print ", Spacing: " & CStr(.Mat.RowSpacing) & " twips";
End If
Debug.Print
Debug.Print indent & "Args count: " & CStr(.Args.Count)
For i = 1 To .Args.Count
Debug.Print indent & " Arg " & CStr(i) & ":-"
Call documentOMathFunctions(.Args(i).Functions, indent)
Next
Case WdOMathFunctionType.wdOMathFunctionNary
' An N-Ary function, such as a summation operator, product operator
' various types of integral operator and so on.
' AFAICS all current N-Ary operators are in effect 3-Ary, i.e.
' The lower limit is the Sub, the upper limit is the Sup, and the
' thing being summed/integrated etc. is the 'Base'
' ignore .SubSupLim for now
.Nary.Char = &H2AFF
Debug.Print indent & "N-ary function, Type character: " & _
oMathNaryOpName(.Nary.Char) & ", Grow? " & YN(.Nary.Grow) & ":-"
Debug.Print indent & "N-ary Lower limit:- Hidden? " & YN(.Nary.HideSub)
Call documentOMathFunctions(.Nary.Sub.Functions, indent)
Debug.Print indent & "N-ary Upper limit:- Hidden? " & YN(.Nary.HideSup)
Call documentOMathFunctions(.Nary.Sup.Functions, indent)
Debug.Print indent & "N-ary body:-"
Call documentOMathFunctions(.Nary.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionNormalText
'Used for 'Non-Math text'
Debug.Print indent & "Literal Text: " & debugPrintString(.Range.Text)
Case WdOMathFunctionType.wdOMathFunctionPhantom
' TBD
Case WdOMathFunctionType.wdOMathFunctionRad
Debug.Print indent & "Degree:- (Hidden? " & YN(.Rad.HideDeg)
Call documentOMathFunctions(.Rad.Deg.Functions, indent)
Debug.Print indent & "Radical:-"
Call documentOMathFunctions(.Rad.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionScrPre
' base object with a superscript/subscript *before* the base
' Think this means an obect that has *both* (although one or both
' could be left blank)
' (TBR: Can OMath be used right-to-left, and if so, how
' are properties named/documented as 'to the left of',
' 'to the right of', 'before', 'after' to be interpreted?
' Or are math formulas etc. always expressed as LTR worldwide
' these days (I would guess so!)
Debug.Print indent & "ScrPre Subscript:-"
Call documentOMathFunctions(.ScrPre.Sub.Functions, indent)
Debug.Print indent & "ScrPre Superscript:-"
Call documentOMathFunctions(.ScrPre.Sup.Functions, indent)
Debug.Print indent & "ScrPre Base-"
Call documentOMathFunctions(.ScrPre.E.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionScrSub
' base object with a subscript after the base
Debug.Print indent & "Base:-"
Call documentOMathFunctions(.ScrSub.E.Functions, indent)
Debug.Print indent & "Superscript:-"
Call documentOMathFunctions(.ScrSub.Sub.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionScrSubSup
' base object with subscript and supersript after the base
Debug.Print indent & "ScrSubSup Base-"
Call documentOMathFunctions(.ScrSubSup.E.Functions, indent)
Debug.Print indent & "ScrSubSup Subscript:-"
Call documentOMathFunctions(.ScrSubSup.Sub.Functions, indent)
Debug.Print indent & "ScrSubSup Superscript:-"
Call documentOMathFunctions(.ScrSubSup.Sup.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionScrSup
' base object with supersript after the base
Debug.Print indent & "Base:-"
Call documentOMathFunctions(.ScrSup.E.Functions, indent)
Debug.Print indent & "Superscript:-"
Call documentOMathFunctions(.ScrSup.Sup.Functions, indent)
Case WdOMathFunctionType.wdOMathFunctionText
Debug.Print indent & "Text: " & debugPrintString(getRunTextFromXML(.Range))
Case Else
' we already printed an unknown type message before the select statement.
End Select
End With
End Sub
In Module "Common" there are some Helper routines
usly had Cstr
Function UHex(codepoint As Long) As String
' Form a 4-digit Unicode Hex string
' fix : had 'CStr' instead of 'Hex'
UHex = "U+" & Right("0000" & Hex(codepoint), 4)
End Function
Function debugPrintString(s As String) As String
' Form a string where 1-byte characters are output as is,
' others are output as Unicode Hex strings
' NB, at the moment we do not try to change stuff such as "&" to "&"
Dim i As Long
Dim t As String
t = ""
For i = 1 To Len(s)
If AscW(Mid(s, i, 1)) < 256 Then
t = t & Mid(s, i, 1)
Else
t = t & " " & UHex(AscW(Mid(s, i, 1))) & " "
End If
Next
debugPrintString = t
End Function
Function YN(b As Boolean) As String
If b Then YN = "Y" Else YN = "N"
End Function
Function AB(b As Boolean) As String
If b Then AB = "Above" Else AB = "Below"
End Function
Function getRunTextFromXML(r As Word.Range) As String
' We need a function like this to retrieve text in the Math Font, (e.g. Cambria Math Font,
' which appears to be encoded as ASCII rather than Unicode.
' So if the equation contains a Cambria Math "A", the .Range.Text is returned as "??"
' For later: if the text is *not* in Cambria Math, we probably *don't* want to do this!
' (Could end up inspecting character by character).
' For the moment, use a kludge to get the first run of text in the range.
Dim x As String
Dim i1 As Long
Dim i2 As Long
x = r.WordOpenXML
' FOr an oMath text, we look for m:t rather than w:t
i1 = InStr(1, x, "<m:t>")
i2 = InStr(i1, x, "</m:t>")
getRunTextFromXML = Mid(x, i1 + 5, i2 - i1 - 5)
End Function
In module Enums, there are some more Helper routines to return things such as Enum names as text (if only VBA had better facilities for Reflection!)
Function oMathIsAllowedNaryOp(codepoint As Long) As Boolean
' Perhaps can look up the unicode database rather than hardcode this list
Select Case codepoint
Case &H2140, &H220F To &H2211, &H222B To &H2233, &H22C0 To &H22C3, &H2A00 To &H2A06, &H2A09, &H2AFF
oMathIsAllowedNaryOp = True
Case Else
oMathIsAllowedNaryOp = False
End Select
End Function
Function oMathNaryOpName(codepoint As Long) As String
' Perhaps can look up the unicode database rather than hardcode this list
' and the standard Unicode character names
Select Case codepoint
Case &H2104
oMathNaryOpName = "Double-Struck N-Ary Summation"
Case &H220F
oMathNaryOpName = "N-Ary Product"
Case &H2210
oMathNaryOpName = "N-Ary Coproduct"
Case &H2211
oMathNaryOpName = "N-Ary Summation"
Case &H22C0
oMathNaryOpName = "N-Ary Logical And"
Case &H22C1
oMathNaryOpName = "N-Ary Logical Or"
Case &H22C2
oMathNaryOpName = "N-Ary Intersection"
Case &H22C3
oMathNaryOpName = "N-Ary Union"
Case &H22A0
oMathNaryOpName = "N-Ary Circled Dot Operator"
Case &H22A1
oMathNaryOpName = "N-Ary Circled Plus Operator"
Case &H22A2
oMathNaryOpName = "N-Ary Circled Times Operator"
Case &H22A3
oMathNaryOpName = "N-Ary Union Operator With Dot"
Case &H22A4
oMathNaryOpName = "N-Ary Union Operator With Plus"
Case &H22A5
oMathNaryOpName = "N-Ary Square Intersection Operator"
Case &H22A6
oMathNaryOpName = "N-Ary Square Union Operator"
Case &H22A9
oMathNaryOpName = "N-Ary Times Operator"
Case &H2AFF
oMathNaryOpName = "N-Ary White Vertical Bar"
Case Else
oMathNaryOpName = "(Possibly invalid N-ary opcode: " & UHex(codepoint) & ")"
End Select
End Function
Function OMathShapeTypeName(OMathShapeType As Integer) As String
Select Case OMathShapeType
Case WdOMathShapeType.wdOMathShapeCentered
OMathShapeTypeName = "wdOMathShapeCentered"
Case WdOMathShapeType.wdOMathShapeMatch
OMathShapeTypeName = "wdOMathShapeMatch"
Case Else
OMathShapeTypeName = "(Math Shape Type unknown: " & CStr(OMathShapeType) & ")"
End Select
End Function
Function oMathSpacingRuleName(oMathSpacingRule As Long) As String
Select Case oMathSpacingRule
Case WdOMathSpacingRule.wdOMathSpacing1pt5
oMathSpacingRuleName = "wdOMathSpacing1pt5"
Case WdOMathSpacingRule.wdOMathSpacingDouble
oMathSpacingRuleName = "wdOMathSpacingDouble"
Case WdOMathSpacingRule.wdOMathSpacingExactly
oMathSpacingRuleName = "wdOMathSpacingExactly"
Case WdOMathSpacingRule.wdOMathSpacingMultiple
oMathSpacingRuleName = "wdOMathSpacingMultiple"
Case WdOMathSpacingRule.wdOMathSpacingSingle
oMathSpacingRuleName = "wdOMathSpacingSingle"
Case Else
oMathSpacingRuleName = "(Math Spacing Rule unknown: " & CStr(oMathSpacingRule) & ")"
End Select
End Function
Function OMathVertAlignTypeName(OMathVertAlignType As Integer) As String
Select Case OMathVertAlignType
Case WdOMathVertAlignType.wdOMathVertAlignBottom
OMathVertAlignTypeName = "wdOMathVertAlignBottom"
Case WdOMathVertAlignType.wdOMathVertAlignCenter
OMathVertAlignTypeName = "wdOMathVertAlignCenter"
Case WdOMathVertAlignType.wdOMathVertAlignTop
OMathVertAlignTypeName = "wdOMathVertAlignTop"
Case Else
OMathVertAlignTypeName = "(Math Vertical Alignment Type unknown: " & CStr(OMathVertAlignType) & ")"
End Select
End Function
Notes.
AFAIK the author/designer of the OMath objects and User Interface
(and indeed other aspect of layout in Word) is Murray Sargent III.
His paper on UnicodeMath Describes how the system as a whole is
intended to use Build-Up. But take care, because not everything
mentioned in there is necessarily implemented in all versions of
OMath (which is used across a number of MS Office products). His
Math-in-Office blog can be quite enlightening too.
There are at least two versions of the OMath object documentation -
one for "VBA" and one for .NET. There are some differences (e.g. some
Properties and at least one Function Type enumeration name is missing
from the VBA version. The .NET version is near here and the VBA
version is near here.
At the moment, none of the code I've posted provides anything that would help you modify the Function structure of an Equation, e.g. insert a new function. That's mainly because I haven't got to grips with it yet. Even writing code to insert a piece of Text throws up a number of problems, not least the question of why Math Font text is not encoded as Unicode and what that means when it comes to modifying it. It may in fact be easier to work with the Linear ("not built up" text version rather than the Object model. TBD!
I solved my problem, looping through the equations (OMaths collection), and then, using the WdOMathFunctionType enumeration to find fraction type functions that contained a matrix in their numerator, I could properly set the matrix properties:
For Each eq In ActiveDocument.OMaths
For Each Func In eq.Functions
If Func.Type = 7 Then 'a fraction function
If Func.Args(1).Functions(1).Type = 12 Then 'a matrix function in the numerator
With Func.Args(1).Functions(1).Mat
.ColGapRule = wdOMathSpacingExactly
.ColGap = 1
.PlcHoldHidden = True
End With
End If
End If
Next
Next
I knew the type of structured equations my document contained, so I didn't include many check conditions. (There probably a more elegant and robust way to search all 'child functions' of equations until the last node is reached.) Hopefully this can serve as a template for anyone trying to expose specific OMath Function properties.

How to code an sql string in vba to use greater than or less than symbols in my MS Access form, when searching for dates in one textbox

I have a list of records that I can search by inputting various date or selecting between dates, and using sql script it returns the results. But I would like to know how can I use greater than symbol in the date textbox and have sql script recognize it and return dates greater than for example ">7/1/2021"
Thank you for your help.
The way that I would do this is to use a combo box that has the various operators (<=,<,=,>,>=) stored as a value list, in addition to the text box that has the date. This is a good idea as you limit the choices that the user can make for the comparison, and you still get to use the input mask for the date field in the combo box.
You can then concatenate the comparison and the date to a SQL string that provides the search. Something like:
Private Sub cmdSearch_Click()
On Error GoTo E_Handle
Dim strSQL As String
Const JetDateFmt = "\#mm\/dd\/yyyy\#;;;\N\u\l\l"
strSQL = "SELECT * " _
& " FROM TB_Edditions " _
& " WHERE ED_Start_Date " & Me!cboOperator & Format(Me!txtDate, JetDateFmt) _
& " ORDER BY ID ASC;"
Me!lstResult.RowSource = strSQL
sExit:
On Error Resume Next
Exit Sub
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "frmDateSearch!cmdSearch_Click", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume sExit
End Sub
In this instance I am using the SQL created to populate a list box. You will note that I am forcing the date entered to be in a specific format, so that it is interpreted correctly.
It is unclear how you are invoking the sql query and that matters, but one approach is you can access public functions anywhere in access. So if you have public functions GetOperator and GetDate you can either pass them as parameters to the sql query, bind them to the sql query directly, or use them in the designer:
For a case like < or > and a date you want to validate the textbox entry first. I tried to validate using the textbox validation rule but apparently this case was too complicated. I ended up having to use the textbox BeforeUpdate Event.
Private Sub txtFilter_BeforeUpdate(Cancel As Integer)
'text box name is txtFilter
If isValidOperator(Me.txtFilter) And isValidDate(Me.txtFilter) Then
Cancel = False
setmysearchcriteria = Me.txtFilter
Else
MsgBox "Search criteria must be in the form of operatordate such as <4/12/21", vbOKOnly
Cancel = True
Exit Sub
End If
End Sub
Public Function isValidOperator(strText As String) As Boolean
Dim operator As String
operator = Left(strText, 1)
If operator = ">" Or operator = "<" Then
SetOperator (operator)
isValidOperator = True
Else
isValidOperator = False
End If
End Function
Private Function isValidDate(strDate As String) As Boolean
strDate = Mid(strDate, 2)
If IsDate(strDate) Then
SetDate (strDate)
isValidDate = True
Else
isValidDate = False
End If
End Function
Here is the code for the public functions serving as synthetic properties. Just pass the Get functions as parameters or invoke them as functions directly in the sql.
Private strMyDate As String
Private myoperator As String
Public Function SetDate(strDate As String) As Boolean
strMyDate = "#" & strDate & "#"
SetDate = True
End Function
Public Function GetDate() As String
GetDate = strMyDate
End Function
Public Function SetOperator(operator As String) As Boolean
myoperator = operator
SetOperator = True
End Function
Public Function GetOperator() As String
GetOperator = myoperator
End Function
In most case you will want to trigger your sql in the textbox AfterUpdate event. For instance Changes to the form your textbox is in will not show if you trigger your sql in the BeforeUpdate event.
Private Sub txtFilter_AfterUpdate()
'trigger your sql here instead of setting the filter
Me.Filter = "ADate " & GetOperator & " " & GetDate
Debug.Print Me.Filter
Me.FilterOn = True
Me.Requery
End Sub
Like #braX said, enclose your date literals inside two # characters..

MS-ACCESS VBA Multiple Search Criteria

In my GUI, I have several ways to filter a database. Due to my lack of knowledge, my VBA programming has exploded with nested IF statements. I am getting better at using ACCESS now, and would like to find a more succinct way to perform multiple filters. My form is continuous.
Is there a simple way to do the following task (I made a toy model example):
I have a combo box SITE where I can filter by work sites A, B, C. After filtering by SITE, I have three check boxes where the user can then filter by item number 1-10, 11-20, 21-30, depending on what the user selects.
Is there a way to append multiple filters (or filter filtered data)? For example, filter by SITE A, then filter A by item number 1-10?
Currently, for EACH check box, I then have an IF statement for each site. Which I then use Form.Filter = . . . And . . . and Form.FilterOn = True.
Can I utilize SQL on the property sheet to filter as opposed to using the VBA?
What I do for these types of filters is to construct a SQL statement whenever one of the filter controls is changed. All of them reference the same subroutine to save on code duplication.
What you do with this SQL statement depends on what you're trying to do. Access is pretty versatile with it; use it as a RecordSource, straight execute it, and use the results for something else, even just printing it to a label.
To try to modularize the process, here's an example of how I do it:
Dim str As String
str = "SELECT * FROM " & Me.cListBoxRowSource
Me.Field1.SetFocus
If Me.Field1.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField1 LIKE '*" & Me.Field1.Text & "*'"
End If
Me.Field2.SetFocus
If Me.Field2.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField2 LIKE '*" & Me.Field2.Text & "*'"
End If
Me.Field3.SetFocus
If Me.Field3.Text <> "" Then
str = AppendNextFilter(str)
str = str & " SQLField3 LIKE '*" & Me.Field3.Text & "*'"
End If
Me.cListBox.RowSource = str
Variables edited to protect the guilty.
My AppendNextFilter method just checks to see if WHERE exists in the SQL statement already. If it does, append AND. Otherwise, append WHERE.
Making quite a few assumptions (since you left out a lot of info in your question), you can do something like this:
Dim sSql as String
sSql = "Select * from MyTable"
Set W = Me.cboSite.Value
sSql = sSql & " WHERE MySite = " & W & ""
Set X = Me.Chk1
Set Y = Me.Chk2
Set Z = Me.Chk3
If X = True Then
sSql = sSql & " And MyItem between 1 and 10"
If Y = True Then
sSql = sSql & " And MyItem between 11 and 20"
If Z = True Then
sSql = sSql & " And MyItem between 21 and 30"
End If
DoCmd.ExecuteSQL sSql
Again, this is entirely "air code", unchecked and probably needing some edits as I haven't touched Access in some time and my VBA is likely rusty. But it should put you on the right track.
The way i use combobox filtering in access is first I design a Query that contains all the data to be filtered. The Query must contain fields to be used for filtering. QueryAllData => "SELECT Table.Site, Table.ItemNumber, FROM Table;" Then make a copy of the query and Name it QueryFilteredData and Design the report to display the data using QueryFilteredData.
Then create a form with a Site ComboBox, ItemNumber Combo Box, and Sub Report Object and Assign SourceObject the Report Name. Use Value List as the combo box Row Source type and type in the values for Row Source to get it working. To get the report to update I always unassign the SubReport.SourceOject update the QueryFilteredData and then Reassign the SubReport.SourceObject
Combobox_Site_AfterUpdate()
Combobox_ItemNumber_AfterUpdate
End Sub
Combobox_ItemNumber_AfterUpdate()
Select Case Combobox_ItemNumber.value
Case Is = "1-10"
Store_Filters 1,10
Case Is = "11-20"
Store_Filters 11,20
Case Is = "21-30"
Store_Filters 21,30
Case Else
Store_Filters 1,10
End Sub
Private Sub Store_Filters(Lowest as integer, Highest as integer)
Dim SRpt_Recset As Object
Dim Temp_Query As Variant
Dim Temp_SourceObject as Variant
Temp_SourceObject = SubReport.SourceObject
SubReport.SourceObject =""
Set SRpt_Recset = CurrentDb.QueryDefs("QueryFilteredData")
Filter_Combo_Box1 = " ((QueryAllData.[Sites])= " & Chr(39) & Combo_Box1 & Chr(39) & ") "
Filter_Combo_Box2 = (Filter_Combo_Box1 AND (QueryAllData.ItemNumber <= Highest)) OR (Filter_Combo_Box1 AND (QueryAllData.ItemNumber >= Lowest));"
Temp_Query = " SELECT " & Query_Name & ".* " & _
"FROM " & Query_Name & " " & _
"WHERE (" & Filter_Combo_Box2 & ") ORDER BY [Field_Name_For_Sorting];"
SRpt_Recset.SQL = Temp_Query
'Debug.print Temp_Query
SubReport.SourceObject = Temp_SourceObject
End Sub
After the Combo Boxes Work if the Data is going to Change like Site and Item Number then you might want to change the Row Source of the combo boxes to Use a Query that uses Select Distinct Site From QueryAllData. I don't know if Filter_Combo_Box2 step so it may need some correction. Hope this helps.

VBA - Find all numbered lines in VBE Modules via pattern search

Task:
My goal is to find all numbered lines in procedures of my Code Modules.
The CodeModule.Find method can be used to check for search terms (target parameter).
Syntax:
object.Find(target, startline, startcol, endline, endcol [, wholeword] [, matchcase] [, patternsearch])
The referring help site https://msdn.microsoft.com/en-us/library/aa443952(v=vs.60).aspx states:
parameter patternsearch: Optional. A Boolean value specifying whether or not the target string is a regular expression pattern.
If True, the target string is a regular expression pattern. False is the default.
As explained above the find method allows a regex pattern search, which I would like to use in order to identify numbered lines in a precise way:
digits followed by a tab. The example below therefore defines a search string s and sets the last parameter PatternSearch in the .Find method to True.
Problem
AFAIK a valid regex definition could be
s = "[0-9]{1,4}[ \t]"
but that doesn't show anything, not even an error.
In order to show at least any results, I defined the search term
s = "[0-9]*[ \t]*)"
in the calling example procedure ListNumberedLines showing erratic results.
Question
Is there any possibility to use a valid regex patternsearch in the CodeModule.Find method?
Example code
Option Explicit
' ==============
' Example Search
' ==============
Sub ListNumberedLines()
' Declare search pattern string s
Dim S As String
10 S = "[0-9]*[ \t]*)"
20 Debug.Print "Search Term: " & S
30 Call findWordInModules(S)
End Sub
Public Sub findWordInModules(ByVal sSearchTerm As String)
' Purpose: find modules ('components') with lines containing a search term
' Method: .CodeModule.Find with last parameter patternsearch set to True
' Based on https://www.devhut.net/2016/02/24/vba-find-term-in-vba-modulescode/
' VBComponent requires reference to Microsoft Visual Basic for Applications Extensibility
' or keep it as is and use Late Binding instead
' Declare module variable oComponent
Dim oComponent As Object 'VBComponent
For Each oComponent In Application.VBE.ActiveVBProject.VBComponents
If oComponent.CodeModule.Find(sSearchTerm, 1, 1, -1, -1, False, False, True) = True Then
Debug.Print "Module: " & oComponent.Name 'Name of the current module in which the term was found (at least once)
'Need to execute a recursive listing of where it is found in the module since it could be found more than once
Call listLinesinModuleWhereFound(oComponent, sSearchTerm)
End If
Next oComponent
End Sub
Sub listLinesinModuleWhereFound(ByVal oComponent As Object, ByVal sSearchTerm As String)
' Purpose: list module lines containing a search term
' Method: .CodeModule.Find with last parameter patternsearch set to True
Dim lTotalNoLines As Long 'total number of lines within the module being examined
Dim lLineNo As Long 'will return the line no where the term is found
lLineNo = 1
With oComponent ' Module
lTotalNoLines = .CodeModule.CountOfLines
Do While .CodeModule.Find(sSearchTerm, lLineNo, 1, -1, -1, False, False, True) = True
Debug.Print vbTab & "Zl. " & lLineNo & "|" & _
Trim(.CodeModule.Lines(lLineNo, 1)) 'Remove any padding spaces
lLineNo = lLineNo + 1 'Restart the search at the next line looking for the next occurence
Loop
End With
End Sub
As #MatsMug says, parsing VBA with Regex is hard impossible, but line-numbers are a simpler case, and should be findable with regex alone.
Fortunately, line numbers can only appear within a procedure body (including before the End Sub/Function/Property statement), so we know they'll never be the first line of your code.
Unfortunately, you can prefix a line-label with 0 or more line continuations:
Sub Foo()
_
_
10 Beep
End Sub
Furthermore, a line number isn't always followed by a space - it can be followed by an instruction separator, giving the line-number the appearance of a line-label:
Sub foo()
10: Beep
End Sub
And if you're code is evil, you might encounter a negative line-number (entered by using hex notation - which VBE dutifully pretty prints back to the code-pane with a leading space and a negative number):
Sub foo()
10 Beep
-1 Beep
End Sub
And we also need to be able to identify numbers that appear on a continued line, that aren't line-numbers:
Sub foo()
Debug.Print _
5 & "is not a line-number"
End Sub
So, here's some evil line-numbering, with a mix of all of those edge-cases:
Option Explicit
Sub foo()
5: Beep
_
_
_
10 Beep
20 _
'Debug.Print _
30
50: Beep
40 Beep
_
-1 _
Beep 'The "-1" line number is achieved by entering "&HFFFFFFFF"
Debug.Print _
2 & "is not a line-number"
60 End Sub
And here's some regex that identifies the line-numbers:
(?<! _)\n( _\n)* ?(?<line_number>(?:\-)?\d+)[: ]
And here's a syntax highlight from regex101:
For the longest time, Rubberduck was struggling with properly/formally parsing line numbers - our work-around was to remove them (replacing them with spaces) before feeding the code module contents to our parser.
Recently we've managed to formally define line numbers:
// lineNumberLabel should actually be "statement-label" according to MS VBAL but they only allow lineNumberLabels:
// A <statement-label> that occurs as the first element of a <list-or-label> element has the effect
// as if the <statement-label> was replaced with a <goto-statement> containing the same
// <statement-label>. This <goto-statement> takes the place of <line-number-label> in
// <statement-list>.
listOrLabel :
lineNumberLabel (whiteSpace? COLON whiteSpace? sameLineStatement?)*
| (COLON whiteSpace?)? sameLineStatement (whiteSpace? COLON whiteSpace? sameLineStatement?)*
;
sameLineStatement : blockStmt;
And lineNumberLabel is defined as:
//Statement labels can only appear at the start of a line.
statementLabelDefinition : {_input.La(-1) == NEWLINE}? (combinedLabels | identifierStatementLabel | standaloneLineNumberLabel);
identifierStatementLabel : unrestrictedIdentifier whiteSpace? COLON;
standaloneLineNumberLabel :
lineNumberLabel whiteSpace? COLON
| lineNumberLabel;
combinedLabels : lineNumberLabel whiteSpace identifierStatementLabel;
lineNumberLabel : numberLiteral;
(full Antlr4 grammar here)
Notice the predicate {_input.La(-1) == NEWLINE}?, which force the parser rule to only match a statementLabelDefinition at the start of a line - a logical line of code.
You see VBA code has physical code lines, like what you're getting from the CodeModule's contents. But VBA code also has a concept of logical code lines, and it turns out that is all the parser cares about.
This would trip any typical regex:
Sub DoSomething()
Debug.Print _
42
End Sub
There's only 1 logical line of code between the signature and the End Sub token, but a simple Find will happily consider that 42 as a "line number" ...which it isn't - it's the argument passed to Debug.Print, in the same instruction, on the same logical code line, but on the next physical code line.
And you can't be dealing with logical code lines without first pre-processing your input, to take line continuation tokens into account.
And in order to do that, you need to actually parse the instructions you're seeing - at least know where they start and where they end... and that's no small undertaking! see ThunderFrame's answer
The VBIDE API is extremely limited, and won't be helpful for that.
TL;DR: You can't parse VBA code with regular expressions alone. So, nope. Sorry! you need a much more complex regex pattern than that - see ThunderFrame's answer.
Conclusion regarding CodeModule.Find via search pattern
Firstly, CodeModule.Find doesn't help via search pattern and its possible use is intransparent.
I agree that the VBIDE API is extremely limited and that there exist excellent professional tools which I highly recommand for any programmer :-)
Consequence: Work around via XML
Secondly I prefer household remedies if possible, so I tried to find an alternative solution using only the helpful parts of VBIDE.
Method
That is why I tried a simple xml conversation of the CodeModule.Lines allowing a flexible search within logical lines.
Instead of using regular expressions in requesting the xml data, I demonstrate a method to find leading numbers via a well defined XPath search (loop thru node list),
thus resolving most problems shown by #ThunderFrame. The search string in function showErls is defined as "line[substring(translate(.,'0123456789','¹¹¹¹¹¹¹¹¹¹'),1,1)="¹"]"
Furthermore function 'lineNumber' returns the logical line number within the module.
Note: To keep it simple, the search is restrained to one module only (user defined constant MYMODULE) and code avoids any regex.
Work around code - main sub
Option Explicit
' ==========================================
' User defined name of module to be analyzed
' ==========================================
Const MYMODULE = "modThunderFrame" ' << change to existing module name or userform
' Declare xml file as object
Dim xCMods As Object ' Late Binding; instead of Early Bd: Dim xCMods As MSXML2.DOMDocument6
Public Sub TestLineNumbers()
' =================
' A. Load/refresh code into xml
' =================
' set xml into memory - contains code module(s) lines
Set xCMods = CreateObject("MSXML2.Domdocument.6.0") ' L.Bd.; instead of E.Bd: Set xCMods = New MSXML2.DOMDocument60
xCMods.async = False
xCMods.validateOnParse = False
' read in user defined code module and load xml, if failed show error message
refreshCM MYMODULE
If xCMods Is Nothing Then Exit Sub
' ======================
' B. search line numbers
' ======================
showERLs
' =============================
' C. Save xml if needed
' =============================
' xCMods.Save ThisWorkbook.Path & "\VBE(" & MYMODULE & ").xml"
' MsgBox "Successfully exported Excel data to " & ThisWorkbook.Path & "\VBE(" & MYMODULE & ").XML!", _
' vbInformation, "Module " & MYMODULE & " to xml"
' =================
' D. terminate xml
' =================
Set xCMods = Nothing
End Sub
Sub procedures
Private Sub showERLs()
' Purpose: [B.] declare XPath search string and define special translate character
Dim s As String
Dim S1 As String: S1 = Chr(185) ' superior number 1 (hex B9) replaces any digit
' declare node and node list
Dim line As Object
Dim lines As Object
' define XPath search string for first digit in line (usual case)
s = "line[substring(translate(.,'0123456789','" & String(10, S1) & "'),1,1)=""" & _
S1 & _
"""]"
' start debugging
Debug.Print "**search string=""" & s & """" & vbNewLine & String(50, "-")
Debug.Print "Line #|Line Content" & vbNewLine & String(50, "-"); ""
' set node list
Set lines = xCMods.DocumentElement.SelectNodes(s)
' -------------------
' loop thru node list
' -------------------
For Each line In lines
Debug.Print Format(lineNumber(line), "00000") & "|" & line.Text ' return logical line number plus line content
Next line
End Sub
Private Sub refreshCM(sModName As String)
' Purpose: [A.] load xml string via LoadXML method
Dim sErrTxt As String
Dim line As Object
Dim lines As Object
Dim xpe As Object
Dim s As String ' xpath expression
Dim pos As Integer ' position of line number prefix
' ======================================
' 1. Read code module lines and load xml
' ======================================
If Not xCMods.LoadXML(readCM(sModName)) Then
' set ParseError object
Set xpe = xCMods.parseError
With xpe
sErrTxt = sErrTxt & vbNewLine & String(20, "-") & vbNewLine & _
"Loading Error No " & .ErrorCode & " of xml file " & vbCrLf & _
Replace(" " & Replace(.URL, "file:///", "") & " ", " ", "[No file found]") & vbCrLf & vbCrLf & _
xpe.reason & vbCrLf & _
"Source Text: " & .srcText & vbCrLf & _
"char?: " & """" & Mid(.srcText, .linepos, 1) & """" & vbCrLf & vbCrLf & _
"Line no: " & .line & vbCrLf & _
"Line pos: " & .linepos & vbCrLf & _
"File pos.: " & .filepos & vbCrLf & vbCrLf
End With
MsgBox sErrTxt, vbExclamation, "XML Loading Error"
Set xCMods = Nothing
Exit Sub
End If
' 2. resolve hex input problem of negative line numbers with leading space (thx #Thunderframe)
s = "line"
Set lines = xCMods.DocumentElement.SelectNodes(s)
' loop thru all logical lines
For Each line In lines
pos = ErlPosInLine(line.Text)
If pos <= Len(line.Text) Then
' to do: add attribute to line node, if wanted
' correct line content
line.Text = Mid(line.Text, pos)
End If
Next
End Sub
Private Function lineNumber(node As Object) As Long
' Purpose: [B.] return logical line number within code module lines
' Param.: IXMLDomNode
' Method: XPath via preceding-sibling count plus one
Dim tag As String: tag = "line"
lineNumber = node.SelectNodes("preceding-sibling::" & tag).Length + 1
End Function
Private Function readCM(Optional modName = "*") As String
' Purpose: return code module line string (VBIDE) of a user defined module to be read into xml
' Call: called from [A.] refreshCM
' xCMods.LoadXML(readCM(sModName))
' Declare variable
Dim s As String
Dim md As CodeModule
If modName = "*" Then Exit Function
On Error GoTo OOPS
' get code module lines into string
Set md = Application.VBE.ActiveVBProject.VBComponents(modName).CodeModule ' MSAccess: Modules("modVBELines")
' change to xml tags
s = getTags(md.lines(1, md.CountOfLines))
' return
readCM = s
OOPS:
End Function
Private Function getTags(ByVal s As String, Optional mode = False) As String
' Purpose: prepares xml string to be loaded
' define constant
Const HEAD = "<?xml version=""1.0"" encoding=""utf-8""?>" & vbCrLf & "<cm>" & vbCrLf
' 1. change tag characters
s = Replace(Replace(s, "<", "<"), ">", ">")
' 2. change special characters (ampersand)
s = Replace(s, "&", "&")
' 3. change "_" points
s = Replace(s, "_" & vbCrLf, Chr(133) & vbLf)
' 4. define logical line entities
If Right(s, 2) = vbCrLf Then s = Left(s, Len(s) - 2)
s = HEAD & " <line>" & Replace(s, vbCrLf, "</line>" & vbCrLf & " <line>") & "</line>" & vbCrLf & "</cm>"
' debug xml tags if second function parameter is true (mode = True)
If mode Then Debug.Print s
' return
getTags = s
End Function
Sub testErlPosInLine()
' Purpose: Test Thunderframe's problem with ERL prefixes (underscores, " ",..) and hex inputs
Dim s As String
s = " _" & vbLf & " -1 xx"
MsgBox "|" & Mid(s, ErlPosInLine(s)) & "|" & vbNewLine & _
"prefix = |" & Mid(s, 1, ErlPosInLine(s) - 1) & "|"
End Sub
Private Function ErlPosInLine(ByVal s As String) As Integer
' Purpose: remove prefix (underscore, tab, " ",.. ) from numbered line
' cf: http://stackoverflow.com/questions/42716936/vba-to-remove-numbers-from-start-of-string-cell
Dim i As Long
For i = 1 To Len(s) ' loop each char
Select Case Mid$(s, i, 1) ' examine current char
Case " " ' permitted chars
Case "_"
Case vbLf, Chr(133), Chr(34)
Case "0" To "9": Exit For ' cut off point
Case Else: Exit For ' i is the cut off point
End Select
Next
If Mid$(s, i, 1) = "-" And Len(s) > 1 Then
If IsNumeric(Mid$(s, i + 1, 1)) Then i = i + 1
End If
' return
ErlPosInLine = i
' debug.print Mid$(s, i) '//strip lead
End Function

How do I automate the built in "find and replace" function in MS-Access?

Is there a way to automate the find and replace function for MS Access?
I've got a lot of data I need to obscure (names and addresses), in a non-reversible way. It's going to an outside contractor that can't see the information (no NDA, etc. will do). But otherwise, I want the data to look as real as possible.
My plan right now is to do a find-n-replace on each character a-z and replace it with a random character. I recognise that chances are, I'll likely end up mapping two or more characters to the same value (which is not a bad thing in my books).
Ideally I'd like to have some kind of function that looks something like:
autoFindNReplace ("table name", "field name", _
"search char", random_alpha_generator(), _
DO_ALL_RECORDS)
And then I can run loop that on each field on each table that I have obscure.
My alternate methods are to:
walk each table and obscure each field individually.
try to come up with some sql statement that will do the same as the mythical autoFindNReplace I describe above.
You can just write a quick hash function in a VBA module and call it from a SQL Update query. Here's an example with table "Table1", and the field "address". The hashField code was taken from here.
Sub MaskAddress()
'Change 1234 to whatever key you'd like.
DoCmd.RunSQL "UPDATE Table1 SET address = hashField(address, 1234)"
End Sub
Public Function hashField(strIn As String, lngKey As Long) as String
Dim i As Integer
Dim strchr As String
For i = 1 To Len(strIn)
strchr = strchr & CStr(Asc(Mid(strIn, i, 1)) Xor lngKey)
Next i
hashField = strchr
End Function
Here was my solution:
Sub autoFindAndReplace(TableName As String, _
FieldName As String, _
Search As String, _
Replace As String)
Dim UpdateString As String
UpdateString = ("update " & TableName & _
" set " & FieldName & _
" = replace (" & FieldName & ", " & _
"""" & Search & """, """ & Replace & """)")
CurrentDb.Execute (UpdateString)
End Sub
Then I loop on autoFindAndReplace with my random character generator, once for alphas and once for numerics.
Yes, I could have done it with multiple Update statements - however, I had a lot of tables and fields to deal with, and this made it look cleaner.