How to select part of a text field using ms word macros - vba

I am trying to build template invoices for Xero. Xero looks for specific fields in your MS Word template and inputs the variable assigned to that text field name in your given format. In word you can toggle the field code to view as just the field name:
«InvoiceNumber»
or the name with format:
{ MERGEFIELD InvoiceNumber \* MERGEFORMAT}
This outputs: INV1234 successfully into the template. Now what I need to do is output just the last 4 characters.
This post seems to imply it must be done with a VBA. I put together a macro with Visual Basic in word and this is where I have hit trouble:
Sub InvoiceNumber()
Dim MyInv As FormFields
Set MyInv = ActiveDocument.FormFields
If MyInv("Text1").Result = "InvoiceNumber" Then
MyInv("Text1").CheckBox.Value = Right(MyInv("Text1"), 4)
End If
End Sub
This returns with
error 5941: The requested member of the selection does not exist
I am quite a beginner with VB macros in word, what am I doing wrong and how should I instead be trying to call the InvoiceNumber Field?

Please try with the following solution:
Sub InvoiceNumber()
Dim MyInv As Field
Set MyInv = GetFieldByName("InvoiceNumber")
If Not MyInv Is Nothing Then
'do something with field result...
'here... debug to Immediate window
Debug.Print Right(MyInv.Result, 4)
End If
End Sub
Function GetFieldByName(fName As String) As Field
Dim F As Field
For Each F In ActiveDocument.Fields
'if not working try with (1) istead of (2) in line below
If Split(Replace(F.Code, " ", " "), " ")(2) = fName Then
Set GetFieldByName = F
Exit Function
End If
Next F
Set GetFieldByName = Nothing
End Function

Related

Problems with MS Word DocVariables defined in VBA

Background: a proprietary piece of veterinary software generates a document pre-populated with merge fields containing data for a particular patient.
The field I am interested in is weight but its a string (Top_Stat) that looks like this "24.5 kg".
I have created a script to read that field and convert it into an integer. However I now want to use this integer to male medication dose calculations based on the animal weight.
As create document variables for this but the variable (name and value) gets stored in the document. I want at least the value to be removed but can't seem to get the result with the following script.
Sub GetWeight()
ActiveDocument.Variables("WeightInKg").Delete
WeightInt = ActiveDocument.MailMerge.DataSource.DataFields("Top_Stat").Value
WeightInt = Replace(WeightInt, " kg", "") 'This removes the superfluous text
WeightInt = Val(WeightInt) 'This converts the weight into a number (integer)
ActiveDocument.Variables.Add Name:="WeightInKg", Value:=WeightInt 'Add the Word variable
ActiveDocument.Fields.Update
End Sub
What am I missing? Apologies, I am new to VBA.
Your code needs some error checking. This first time it is run the document variable "WeightInKg" does not exist and when you go to delete it, the routine errors out.
Document variables, not to be confused with VBA Subroutine variables are not Word document fields so unless you have another reason for updating all fields, that code line is unnecessary.
Finally, you should always declare your VBA Subroutine variables.
I have modified your code but could not fully test it because I don't have your mail merge data source ... but give it a try and see if it now works for you.
Sub AutoOpen()
Call GetWeight
End Sub
Sub GetWeight()
Dim WeightIn As Long
On Error Resume Next
ActiveDocument.Variables("WeightInKg").Delete
On Error GoTo ErrHandler
WeightInt = ActiveDocument.MailMerge.DataSource.DataFields("Top_Stat").Value
WeightInt = Replace(WeightInt, " kg", "") 'This removes the superfluous text
WeightInt = Val(WeightInt) 'This converts the weight into a number (integer)
ActiveDocument.Variables.Add Name:="WeightInKg", Value:=WeightInt 'Add the Word variable
' ActiveDocument.Fields.Update
ErrHandler:
If Err.Number > 0 Then
MsgBox Err.Number & vbCr & Err.Description, vbCritical
Err.Clear
End If
End Sub
This is the screenshot of the Word document I am trying to populate.
Screenshot

What does a hyperlink range.start and range.end refer to?

I'm trying to manipulate some text from a MS Word document that includes hyperlinks. However, I'm tripping up at understanding exactly what Range.Start and Range.End are returning.
I banged a few random words into an empty document, and added some hyperlinks. Then wrote the following macro...
Sub ExtractHyperlinks()
Dim rHyperlink As Range
Dim rEverything As Range
Dim wdHyperlink As Hyperlink
For Each wdHyperlink In ActiveDocument.Hyperlinks
Set rHyperlink = wdHyperlink.Range
Set rEverything = ActiveDocument.Range
rEverything.TextRetrievalMode.IncludeFieldCodes = True
Debug.Print "#" & Mid(rEverything.Text, rHyperlink.Start, rHyperlink.End - rHyperlink.Start) & "#" & vbCrLf
Next
End Sub
However, the output between the #s does not quite match up with the hyperlinks, and is more than a character or two out. So if the .Start and .End do not return char positions, what do they return?
This is a bit of a simplification but it's because rEverything counts everything before the hyperlink, then all the characters in the hyperlink field code (including 1 character for each of the opening and closing field code braces), then all the characters in the hyperlink field result, then all the characters after the field.
However, the character count in the range (e.g. rEverything.Characters.Count or len(rEverything)) only includes the field result if TextRetrievalMode.IncludeFieldCodes is set to False and only includes the field code if TextRetrievalMode.IncludeFieldCodes is set to True.
So the character count is always smaller than the range.End-range.Start.
In this case if you change your Debug expression to something like
Debug.Print "#" & Mid(rEverything.Text, rHyperlink.Start, rHyperlink.End - rHyperlink.Start - (rEverything.End - rEverything.Start - 1 - Len(rEverything))) & "#" & vbCrLf
you may see results more along the lines you expect.
Another way to visualise what is going on is as follows:
Create a very short document with a piece of text followed by a short hyperlink field with short result, followed by a piece of text. Put the following code in a module:
Sub Select1()
Dim i as long
With ActiveDocument
For i = .Range.Start to .Range.End
.Range(i,i).Select
Next
End With
End Sub
Insert a breakpoint on the "Next" line.
Then run the code once with the field codes displayed and once with the field results displayed. You should see the progress of the selection "pause" either at the beginning or the end of the field, as the Select keeps "selecting" something that you cannot actually see.
Range.Start returns the character position from the beginning of the document to the start of the range; Range.End to the end of the range.
BUT everything visible as characters are not the only things that get counted, and therein lies the problem.
Examples of "hidden" things that are counted, but not visible:
"control characters" associated with content controls
"control characters" associated with fields (which also means hyperlinks), which can be seen if field result is toggled to field code display using Alt+F9
table structures (ANSI 07 and ANSI 13)
text with the font formatting "hidden"
For this reason, using Range.Start and Range.End to get a "real" position in the document is neither reliable nor recommended. The properties are useful, for example, to set the position of one range relative to the position of another.
You can get a somewhat more accurate result using the Range.TextRetrievalMode boolean properties IncludeHiddenText and IncludeFieldCodes. But these don't affect the structural elements involved with content controls and tables.
Thank you both so much for pointing out this approach was doomed but that I could still use .Start/.End for relative positions. What I was ultimately trying to do was turn a passed paragraph into HTML, with the hyperlinks.
I'll post what worked here in case anyone else has a use for it.
Function ExtractHyperlinks(rParagraph As Range) As String
Dim rHyperlink As Range
Dim wdHyperlink As Hyperlink
Dim iCaretHold As Integer, iCaretMove As Integer, rCaret As Range
Dim s As String
iCaretHold = 1
iCaretMove = 1
For Each wdHyperlink In rParagraph.Hyperlinks
Set rHyperlink = wdHyperlink.Range
Do
Set rCaret = ActiveDocument.Range(rParagraph.Characters(iCaretMove).Start, rParagraph.Characters(iCaretMove).End)
If RangeContains(rHyperlink, rCaret) Then
s = s & Mid(rParagraph.Text, iCaretHold, iCaretMove - iCaretHold) & "" & IIf(wdHyperlink.TextToDisplay <> "", wdHyperlink.TextToDisplay, wdHyperlink.Address) & ""
iCaretHold = iCaretMove + Len(wdHyperlink.TextToDisplay)
iCaretMove = iCaretHold
Exit Do
Else
iCaretMove = iCaretMove + 1
End If
Loop Until iCaretMove > Len(rParagraph.Text)
Next
If iCaretMove < Len(rParagraph.Text) Then
s = s & Mid(rParagraph.Text, iCaretMove)
End If
ExtractHyperlinks = "<p>" & s & "</p>"
End Function
Function RangeContains(rParent As Range, rChild As Range) As Boolean
If rChild.Start >= rParent.Start And rChild.End <= rParent.End Then
RangeContains = True
Else
RangeContains = False
End If
End Function

How to get Selection.Text to read displaytext of Macro field code

I'm having some trouble figuring this out, and would really appreciate some help. I'm trying to write a macro that uses the selection.text property as a Case text-expression. When the macro is clicked in Microsoft Word, the selected text is automatically set to the DisplayText. This method worked great for the formatting via Selection.Font.Color for a quick and dirty formatting toggling macro, but it doesn't work for the actual text.
When debugging with MsgBox, it is showing a box (Eg: □ ) as the value.
For example,
Word Field Code:
{ MACROBUTTON Macro_name DisplayText }
VBA Code run when highlighting "DisplayText" in Word:
Sub Macro_name()
Dim Str As String
Str = Selection.Text
MsgBox Str
Select Case Str
Case "DisplayText"
MsgBox "A was selected"
Case "B"
MsgBox "B was selected"
End Select
End Sub
What is output is a Message Box that only shows □
When I run this macro with some regular text selected, it works just fine.
My question is this: Is there a way to have the macro read the displaytext part of the field code for use in the macro?
You can read the field code, directly, instead of the selection (or the Field.Result which also doesn't give the text).
It's not quite clear how this macro is to be used throughout the document, so the code sample below provides two variations.
Both check whether the selection contains fields and if so, whether the (first) field is a MacroButton field. The field code is then tested.
In the variation that's commented out (the simpler one) the code then simply checks whether the MacroButton display text is present in the field code. If it is, that text is assigned to the string variable being tested by the Select statement.
If this is insufficient because the display text is "unknown" (more than one MacroButton field, perhaps) then it's necessary to locate the part of the field code that contains the display text. In this case, the function InstrRev locates the end point of the combined field name and macro name, plus the intervening spaces, in the entire field code, searching from the end of the string. After that, the Mid function extracts the display text and assigns it to the string variable tested by the Select statement.
In both variations, if the selection does not contain a MacroButton field then the selected test is assigned to the string variable for the Select statement.
(Note that for my tests I needed to use Case Else in the Select statement. You probably want to change that back to Case "B"...)
Sub Display_Field_DisplayText()
Dim Str As String, strDisplayText As String
Dim textLoc As Long
Dim strFieldText As String, strMacroName As String
Dim strFieldName As String, strFieldCode As String
strDisplayText = "text to display"
If Selection.Fields.Count > 0 Then
If Selection.Fields(1).Type = wdFieldMacroButton Then
strFieldName = "MacroButton "
strMacroName = "Display_Field_DisplayText "
strFieldCode = strFieldName & strMacroName
Str = Selection.Fields(1).code.text
textLoc = InStrRev(Str, strFieldCode)
strFieldText = Mid(Str, textLoc + Len(strFieldCode))
MsgBox strFieldText
Str = strFieldText
'If InStr(Selection.Fields(1).code.text, strDisplayText) > 0 Then
' Str = strDisplayText
'End If
End If
Else
Str = Selection.text
End If
Select Case Str
Case strDisplayText
MsgBox "A was selected"
Case Else
MsgBox "B was selected"
End Select
End Sub

Catia V5 Macro: Incomplete renaming function

I've been dealing with this a while and even had help but i can't work it out.
The following macro renames PartName or InstanceName depending on user and CADSelection.
Problem is it's not working in PartName alteration.
Can someone help me complete this macro? and ideally explain what i did incorrectly?
Sub CATMain()
If CATIA.Documents.Count = 0 Then
MsgBox "There are no CATIA documents open. Please open a CATIA document and try again.", ,msgboxtext
Exit Sub
End If
If InStr(CATIA.ActiveDocument.Name, ".CATProduct") < 1 Then
MsgBox "The active document is not a Product. Please open a CATIA Product and try again.", ,msgboxtext
Exit Sub
End If
Dim oSelection As Selection
Set oSelection = CATIA.ActiveDocument.Selection
If oSelection.Count < 1 then
MsgBox "Pick some components using cad selection."
Else
'****** Alter Instance Name *****'
Dim msg
msg = MsgBox ("Click ""Yes"" to change Instance Name, ""No"" to change Part Name or ""Cancel"" to exit", _
vbYesNoCancel, "Renaming Tool")
if vbYes = msg then
'****** Inputbox for Instance name alteration *****
Dim NewIName As String
NewIName = InputBox("Please input the desired Instance Name. Example: E","Instance name alteration","E")
'****** Inputbox for Instance number alteration *****
Dim NewINumber As Integer
NewINumber = InputBox("Please input the initial number for the 1st component. Example: 1","Instance numbering alteration","1")
Dim oIBody
Dim InstName As Body
For oIBody = 1 to oSelection.Count
Set InstName = oSelection.Item(oIBody).Value
'****** Instance name alteration *****
InstName.Parent.Parent.ReferenceProduct.Products.Item( _
InstName.Name).Name= NewIName + CStr(NewINumber)
NewINumber=NewINumber+1
Next
elseif vbNo = msg then
'****** Inputbox for Part name alteration *****
Dim NewPName As String
NewPName = InputBox("Please input the desired Part Name. Example: E","Part Name alteration","E")
'****** Inputbox for Part number alteration *****
Dim NewPNumber As Integer
NewPNumber = InputBox("Please input the initial number for the 1st Component. Example: 1","Part numbering alteration","1")
Dim oPBody
Dim PartName As Body
For oPBody = 1 to oSelection.Count
Set PartName = oSelection.Item(oPBody).Value
'****** Part name alteration *****
PartName.ReferenceProduct.Name= NewPName + CStr(NewPNumber)
NewPNumber=NewPNumber+1
Next
End If
End If
oSelection.Clear
End Sub
The part "name" is really the Part Number and is changed using the "PartNumber" property.
So try changing
PartName.ReferenceProduct.Name= NewPName + CStr(NewPNumber)
to
PartName.ReferenceProduct.PartNumber= NewPName + CStr(NewPNumber)
This doesn't influence the document name unless you have not saved your part already.
What else :
1) Your variable naming is confusing. You call the Product "InstName" in one place and "PartName" in another. At first glance I thought those were strings. Using oProduct would be less confusing.
2) You seem real confident that the user has pre-selected the correct types. Since you are selecting in an assembly, instead of using Selection.Item(i).Value, you can use Selection.item(i).LeafProduct which will always be the instance product of whatever object is selected. Even if the user picks a surface, it will return the instance product which contains the selected surface.

Find each word marked as error

Is it possible to find words that MS-Word marks as errors?
My goal is to find words containing "è" instead of "é", but to use a macro I need to replace the char only into words marked as error.
I'm working on MS-Word 2013
here is some code to get you started. you need to add code that checks for the "bad" letter
' this is just demo code that shows how misspelled words could be replaced
' create document with a few words, one or two misspelled
' then single-step this code using F8 key
' while watching the text in the document
Sub aaaaaa()
Dim i As Integer
Dim badChr As String
Dim badWrd As String
Dim wrd As Object
For Each wrd In ActiveDocument.Words
If wrd.SpellingErrors.Count > 0 Then
badWrd = wrd.SpellingErrors(1).Text
Debug.Print badWrd
wrd.SpellingErrors(1).Text = string(len(badWrd),"x") ' replace whole word if you like
wrd.SpellingErrors(1).Text = badWrd ' put back original
For i = 1 To wrd.SpellingErrors(1).Characters.Count ' loop characters in misspelled word
badChr = wrd.SpellingErrors(1).Characters(i).Text
wrd.SpellingErrors(1).Characters(i).Text = "x" ' replace character
wrd.SpellingErrors(1).Characters(i).Text = badChr ' restore character
Next i
End If
Next wrd
End Sub