VBA search for string in document and check its color - string

I tried to make a function to search for a string in a document and check what is the first char in the string that is colored in red.
for example I know that my document contains the string "bread water juice peach wine". Imagine that the bold text is red colored. I want the function to return the int 19 (first red char - p).
Function check(stringToCheck As String) As Integer
Dim oRng As Word.Range
Set oRng = ActiveDocument.Content
With oRng.Find
' to ensure that unwanted formats aren't included as criteria
.ClearFormatting
'You don't care what the text is
.Text = stringToCheck
'Loop for each match and set a color
While .Execute
MsgBox (oRng.Text)
For i = 1 To 40
'take the Nth char of the string an check if it's red
'the following msgBox is working
MsgBox (Mid(oRng, i, 1))
If Mid(Orng, i, 1).Font.Color = wdColorRed Then
'the following msgBox is not working which means the error is in the last line.
MsgBox ("made it")
check = i
Exit Function
End If
Next i
Wend
End With
End Function
every time I try to call the function I have the error "run time error 424 - object required".
I added some msgboxes to see when is the function interrupted and added a comment in that place.
what is the problem? how can I fix it?

First thing's first: Use Option Explicit at the beginning of your module. You'll quickly find that your code has compilation issues.
Do you mean to use oRng or myRange? This should be consistent.
Once you've done that...
Mid(myRange, i, 1) returns a string, not an object.
You may want to use If oRng.Characters(1).Font.Color = wdColorRed Then instead.
Here's your code modified that returns correctly:
Function check(stringToCheck As String) As Integer
Dim oRng As Word.Range
Set oRng = ActiveDocument.Content
Dim i As Integer
With oRng.Find
' to ensure that unwanted formats aren't included as criteria
.ClearFormatting
'You don't care what the text is
.Text = stringToCheck
'Loop for each match and set a color
While .Execute
MsgBox (oRng.Text)
For i = 1 To 40
'take the Nth char of the string an check if it's red
'the following msgBox is working
MsgBox oRng.Characters(i)
If oRng.Characters(i).Font.Color = wdColorRed Then
'the following msgBox is not working which means the error is in the last line.
MsgBox ("made it")
check = i
Exit Function
End If
Next i
Wend
End With
End Function

Related

How can I make removal of Strikethrough text in Excel cells using XML parsing more robust?

I have a complex spreadsheet with many cells of text containing random mixtures of normal text and text with strikethrough. Before I scan a cell for useful information, I have to remove the struck through text. I intially achieved this (with VBA) using the Characters object, but it was so slow as to be totally impractical, for business purposes. I was then kindly supplied with some code (on this site) that parses the XML encoding. This was 1000's of times faster, but it occassionally causes the following error:
"The parameter node is not a child of this node".
So far, it only happens in heavily loaded cells (1000's of characters), otherwise it works fine. I cannot see anything wrong in the code or the XML structure of the problem cells, although I am a total newbie to XML. Using the VBA debugger, I know the error is occurring when RemoveChild() is called, typically when it has already worked without error on a few struck through sections of a cell's text.
Is there a way I could make the following code more robust?
Public Sub ParseCellForItems(TargetCell As Excel.Range, ItemsInCell() As String)
Dim XMLDocObj As MSXML2.DOMDocument60
Dim x As MSXML2.IXMLDOMNode
Dim s As MSXML2.IXMLDOMNode
Dim CleanedCellText As String
On Error GoTo ErrorHandler
Call UnstrikeLineBreakCharsInCell(TargetCell)
Set XMLDocObj = New MSXML2.DOMDocument60
'Add some namespaces.
XMLDocObj.SetProperty "SelectionNamespaces", "xmlns:ss='urn:schemas-microsoft-com:office:spreadsheet' " & _
"xmlns:ht='http://www.w3.org/TR/REC-html40'"
'Load the cell data as XML into XMLDOcObj.
If XMLDocObj.LoadXML(TargetCell.Value(xlRangeValueXMLSpreadsheet)) Then
Set x = XMLDocObj.SelectSingleNode("//ss:Data") 'Cell content.
If Not x Is Nothing Then
Set s = x.SelectSingleNode("//ht:S") 'Struck through cell content.
Do While Not s Is Nothing
x.RemoveChild s
Set s = x.SelectSingleNode("//ht:S")
Loop
CleanedCellText = XMLDocObj.Text
'Parse CleanedCellText for useful information.'
'...
End If
End If
Set XMLDocObj = Nothing
'Presumably don't have to 'destroy' x and s as well, as they were pointing to elements of XMLObj.
Exit Sub
ErrorHandler:
Call RaiseError(Err.Number, Err.Source, "ParseCellForItems()", Err.Description, Erl)
End Sub
Public Sub UnstrikeLineBreakCharsInCell(TargetCell As Excel.Range)
Dim mc As MatchCollection
Dim RegExObj1 As RegExp
Dim Match As Variant
On Error GoTo ErrorHandler
Set RegExObj1 = New RegExp
RegExObj1.Global = True
RegExObj1.IgnoreCase = True
RegExObj1.Pattern = "\n" 'New line. Equivalent to vbNewLine.
Set mc = RegExObj1.Execute(TargetCell.Value)
For Each Match In mc
TargetCell.Characters(Match.FirstIndex + 1, 1).Font.Strikethrough = False
Next Match
Set mc = Nothing
Set RegExObj1 = Nothing
Exit Sub
ErrorHandler:
Call RaiseError(Err.Number, Err.Source, "UnstrikeLineBreakCharsInCell()", Err.Description, Erl)
End Sub
Yep, as per Tim Williams' comment, making sure you're calling RemoveChild() from its immediate parent fixes the problem:
Set s = x.SelectSingleNode("//ht:S")
Do While Not s Is Nothing
s.ParentNode.RemoveChild s
Set s = x.SelectSingleNode("//ht:S")
Loop

Test several text boxes at once for any blanks

I want to check three different textboxes on a form (but not all) to see if any are left blank. Comparable to "If IsBlank," on the spreadsheet. From what I've read, it seems that IsEmpty can't be used this way? I've been playing with IsNull, but haven't found a proper syntax that would allow it to work. Surely there must be some simple, even standard, way of doing this? Maybe some other function I've never heard of?
(I know I can use If Txtbx1.value = "" Or If... (etc.)
—I'm looking for a shorter and more graceful way to do this.)
Thanks!
Consider using OR:
Sub dural()
If Txtbx1.Value = "" Or Txtbx2.Value = "" Or Txtbx3.Value = "" Then
MsgBox "at least one empty"
End If
End Sub
Match vs Array of Text Boxes feat. IsError, VarType and TypeName
All codes were in a user form code sheet and were run via command buttons on the user form where also the three text boxes were located.
In the first code, the result of Match is passed to the var (variant) variable and further evaluated. If there is at least one text box with no value ("" or vbNullString), var will return the position of the first found empty text box 1-based i.e. the first is 1, the second is 2 etc. unlike the Array which is 0-based i.e. the first element is 0, the second is 1 etc.
The second code is a presentation of the three choices that were studied in the first code.
The third code is a 'bad' code without variables you might be looking for.
Sub TextBoxFun()
Dim vntTB As Variant ' Text Box Array
Dim var As Variant ' Match Variant
Dim strTB As String ' Pass String
Dim lngTB As Long ' Pass Long
' Pass TextBoxes to Text Box Array.
vntTB = Array(TextBox1, TextBox2, TextBox3)
' Either:
var = Application.Match("", vntTB, 0)
' Or:
'var = Application.Match(vbNullString, vntTB, 0)
Debug.Print String(10, "'")
Debug.Print "IsError(var) = " & IsError(var) ' True
Debug.Print "VarType(var) = " & VarType(var) ' 10 or vbError
Debug.Print "TypeName(var) = " & TypeName(var) ' Error
Debug.Print String(10, "'")
' Line of Code / vbNullString Found ? >>> ' True False
Debug.Print var ' 1
' Depending on the first position of ' 2
' the found vbNullString or "". ' 3 Error 2042
lngTB = IsError(var): Debug.Print lngTB ' 0 -1
lngTB = VarType(var): Debug.Print lngTB ' 5 10
'lngTB = TypeName(var): Debug.Print lngTB ' Nope Nope
' TypeName returns always a string.
strTB = IsError(var): Debug.Print strTB ' False True
strTB = VarType(var): Debug.Print strTB ' 5 10
strTB = TypeName(var): Debug.Print strTB ' Double Error
End Sub
Sub TextBoxFunConclusion()
Dim vntTB As Variant ' Text Box Array
' Pass TextBoxes to Text Box Array.
vntTB = Array(TextBox1, TextBox2, TextBox3)
If IsError(Application.Match("", vntTB, 0)) Then
Debug.Print "No 'empty' text boxes (via IsError)."
Else
Debug.Print "At least one 'empty' text box (via IsError)."
End If
If VarType(Application.Match("", vntTB, 0)) = 10 Then
Debug.Print "No 'empty' text boxes (via VarType)."
Else
Debug.Print "At least one 'empty' text box (via VarType)."
End If
If TypeName(Application.Match("", vntTB, 0)) = "Error" Then
Debug.Print "No 'empty' text boxes (via TypeName)."
Else
Debug.Print "At least one 'empty' text box (via TypeName)."
End If
End Sub
Sub TextBoxFunMyChoice()
If IsError(Application.Match("", Array(TextBox1, TextBox2, TextBox3), 0)) _
Then
Debug.Print "No 'empty' text boxes (via IsError)."
Else
Debug.Print "At least one 'empty' text box (via IsError)."
End If
End Sub
Private Sub CommandButton1_Click()
TextBoxFun
End Sub
Private Sub CommandButton2_Click()
TextBoxFunConclusion
End Sub
Private Sub CommandButton3_Click()
TextBoxFunMyChoice
End Sub

How do I convert this Excel macro for use in Word?

I have found a macro written for Excel that has the exact function that I need but I don't know how to convert it for use in Word - I'm fairly new to scripting and the only language/syntax that I'm familiar with is the Aspect scripting language that I've been learning from working with Procomm Plus. I found the Excel macro here:
Find specific text and delete three rows above it
Sub Delete()
Dim find As String: find = "TOT"
Dim rng As Range
Set rng = Sheets("Sheet1").Cells.find(What:=find, After:=Sheets("Sheet1").Cells(1, 1), LookIn:=xlValues, Lookat:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=True)
If Not rng Is Nothing Then
rng.EntireRow.Delete
rng.Offset(-1).EntireRow.Delete
rng.Offset(-2).EntireRow.Delete
rng.Offset(-3).EntireRow.Delete
End If
End Sub
I need the macro to look for the the text "% Invalid input detected at ‘^’ marker" and then delete it, as well as the two lines above that phrase:
Router#show environment all ;(let's call this "line -2")
^ ;(and this would be "line -1")
% Invalid input detected at ‘^’ marker ;(and "line 0")
This is the console output from a Cisco router (copy/pasted to Word) and the phrase "% Invalid input detected at ‘^’ marker" is always the same but the two lines above it can vary, depending on what Cisco command I have used that has not been recognised.
I think the solution is probably quite simple but I'm stuck!
I found another macro for Word that performs a similar function and - it looks for a key phrase and deletes the line containing that phrase but again, I'm not sure how to extend it to include the preceding two lines of text:
Sub InvalidInput()
Dim oRng As Word.Range
Set oRng = ActiveDocument.Range
With oRng.Find
.Text = "Invalid input detected"
While .Execute
oRng.Paragraphs(1).Range.Delete
Wend
End With
End Sub
Would anyone be able to suggest some edits that could solve my problem? Thank you very much in advance.
Assuming your 'lines' are separate paragraphs, try:
Sub Demo()
Application.ScreenUpdating = False
With ActiveDocument.Range
With .Find
.ClearFormatting
.Replacement.ClearFormatting
.Text = "[!^13]#^13[!^13]#^13% Invalid input detected at ‘^94’ marker^13"
.Replacement.Text = ""
.Forward = True
.Wrap = wdFindStop
.Format = False
.MatchWildcards = True
.Execute Replace:=wdReplaceAll
End With
End With
Application.ScreenUpdating = True
End Sub
Otherwise, you'll have to provide a more meaningful description of what delineates your 'lines'.
This is a longshot but have a go at this macro in Word ...
Public Sub RemoveUnwantedParagraphs()
Dim objParagraph As Paragraph, arrToRemove() As Long, lngIndex As Long
Dim i As Long, x As Long, strSearchFor As String
strSearchFor = "% Invalid input detected at '^' marker"
lngIndex = -1
For i = 1 To ThisDocument.Paragraphs.Count
Set objParagraph = ThisDocument.Paragraphs(i)
If Left(objParagraph.Range.Text, Len(strSearchFor)) = strSearchFor Then
For x = 2 To 0 Step -1
lngIndex = lngIndex + 1
ReDim Preserve arrToRemove(lngIndex)
arrToRemove(lngIndex) = i - x
Next
End If
Next
On Error GoTo ExitGracefully
For i = UBound(arrToRemove) To 0 Step -1
ThisDocument.Paragraphs(arrToRemove(i)).Range.Delete
Next
ExitGracefully:
End Sub
... do yourself one favour though before you run it, BACKUP YOUR DOCUMENT!!!.
I make no guarantees that it won't inadvertently stuff up the entire thing. I've done limited development in Word but that code above did work for me.
The only thing to note is that if the text you're searching for is contained in the 1st or 2nd row, it may break. I figure that's not expected though so didn't cater for it.

How to deal with a dash in an Excel VBA input variable?

I'm having some trouble with an Excel VBA macro and was hoping you could give me some advice on how to fix it. In the code below, when a user clicks a command button, an InputBox pops up and the user inputs a number in the form XXX-XXXXXX (e.g. 111-222222). Then, the macro takes the value from the column adjacent to button and uses the input variable to replace a certain part of the adjacent column's value. However, when I tried to run the macro and input a number such as 123-456789, nothing happens. I believe it has something to do with the dash that the user inputs, however I'm not sure how to fix it. Please help!
Sub CommandButtonTitleXXXdashXXXXXX_Click()
Application.ScreenUpdating = False
On Error Resume Next
Dim n As Integer
n = Worksheets("REVISIONS").Range("D3:D17").Cells.SpecialCells(xlCellTypeConstants).Count
If n = 15 Then
If MsgBox("Title revision box full. Add manually.", vbOKOnly, "Error") = vbOK Then
Exit Sub
End If
End If
Dim rs As Integer
rs = ActiveSheet.Shapes(Application.Caller).TopLeftCell.Row
Dim amount As String
Application.ScreenUpdating = True
amount = Application.InputBox("Enter case number:", "")
Application.ScreenUpdating = False
If amount = False Then
Exit Sub
Else
Dim newCell As String
newCell = Replace(Worksheets("TITLE").Range("A" & rs).Value, "XXX-XXXXXX", amount)
Worksheets("REVISIONS").Range("D17").End(xlUp).Offset(1, 0) = newCell
End If
End Sub
I would take your code to an extra step.
No need to declare amount as String. You can keep it as a Variant. Also like I mentioned in the comment above
Can your Case number be like #D1-1%#456? If not then you have an additional problem to handle ;)
See this example. I have commented the code so that you will not have a problem understanding it. Still if you do lemme know :) The other way would be to use REGEX to validate your Case ID. Let me know if you want that example as well.
Code
Sub Sample()
Dim amount As Variant
' 123-$456789 <~~ Invalid
' 123-4567890 <~~ Valid
' ABC-&456789 <~~ Invalid
' 456-3456789 <~~ Valid
amount = Application.InputBox("Enter case number:", "")
'~~> Check if user pressed cancel
If amount = False Then Exit Sub
'~~> Check if then Case ID is valid
If IsValidCaseNo(amount) Then
MsgBox amount
Else
MsgBox "Invalid case ID"
End If
End Sub
Function IsValidCaseNo(sAmount) As Boolean
Dim s As String
Dim i As Long, j As Long
s = sAmount
'
'~~> Initial basic checks
'
'~~> Check if the length is 11 characters
If Len(Trim(s)) <> 11 Then GoTo Whoa
'~~> Check if the string contains "-"
If InStr(1, s, "-") = 0 Then GoTo Whoa
'~~> Check if the 4th character is a "-"
If Mid(s, 4, 1) <> "-" Then GoTo Whoa
'~~> Loop through 1st 3 characters and check
'~~> If they are numbers
For i = 1 To 3
Select Case Asc(Mid(s, i, 1))
Case 48 To 57
Case Else: GoTo Whoa
End Select
Next
'~~> Loop through last 6 characters and check
'~~> If they are numbers
For i = 5 To 11
Select Case Asc(Mid(s, i, 1))
Case 48 To 57
Case Else: GoTo Whoa
End Select
IsValidCaseNo = True
Next
Whoa:
End Function
If you Dim amount as String, you can test it as a string:
Sub GetDash()
Dim amount As String
amount = Application.InputBox(Prompt:="Enter case number", Type:=2)
If amount = "False" Then
MsgBox "You cancelled"
End If
End Sub

VBA error handling not working when function being called generates error

I am iterating through rows,and looking up the first column of each row(name) using a different function for finding his marks.
For each "name" there is a particular entry in a different table("marks") which can also be blank or "-"
Sub main()
On error goto errorhandler
Dim name as string
Dim marks as double
Dim source as range
Dim runs as integer
runs = 1
Set source = Sheets("input").Range("$A$2")
i=1
Do until source.offset(i,0) = "" 'iterate through rows
name = source.offset(i,0)
marks = find(name)
do until runs * marks > 100
runs = runs + 1 'since marks is not defined;runs overflows
Loop
'a lot of code which relies on marks
errorhandler:
i = i + 1
Loop
End Sub
Function find(name as string) as double
find = application.vlookup(name,Sheets("values").Range("$A$2,$C$5"),2,0)
End function
now as i said the value in column 2 of that table can also be blank or "-" and thus results in error Runtime error 13 "Type mismatch"
i even tried putting on error statement inside the loop
VBA should normally search for error handling in the calling function i.e "main" but its not doing so
TRIED AND TESTED
Sub main()
On Error GoTo errorhandler
Dim name As String
Dim marks As Double
Dim source As Range
Set source = Sheets("input").Range("$A$2")
i = 1
Do Until source.Offset(i, 0) = "" 'iterate through rows
name = source.Offset(i, 0)
marks = find(name)
Debug.Print marks
i = i + 1
Loop
Exit Sub
errorhandler:
MsgBox Err.Description
End Sub
Function find(name As String) As Double
find = Application.WorksheetFunction.VLookup(name, Sheets("values").Range("$A$2:$C$5"), 2, False)
End Function
EDIT: Kartik, Sorry, I didn't see that you have already accepted the answer.
FOLLOWUP
actually i dont want to print any error message instead straightaway skip to the next iteration – Kartik Anand 14 secs ago
In that case you are handling error in the wrong section ;)
Try this
Sub main()
Dim name As String
Dim marks As Double
Dim source As Range
Set source = Sheets("input").Range("$A$2")
i = 1
Do Until source.Offset(i, 0) = "" 'iterate through rows
name = source.Offset(i, 0)
marks = find(name)
Debug.Print marks
i = i + 1
Loop
End Sub
Function find(name As String) As Double
On Error GoTo earlyexit
find = Application.WorksheetFunction.VLookup(name, Sheets("values").Range("$A$2:$C$5"), 2, False)
Exit Function
earlyexit:
find = 0
End Function
Add an Err.Clear after errorhandler:
Also, see the Excel help on Err.Clear which reccomends On Error Resume Next
together with If Err.Number <> 0 Then
This will produce much clearer code
Something like
Sub main()
On Error Resume Next
Dim name As String
Dim marks As Double
Dim source As Range
Set source = Sheets("input").Range("$A$2")
i = 1
Do Until source.Offset(i, 0) = "" 'iterate through rows
name = source.Offset(i, 0)
marks = Find(name)
If Err.Number <> 0 Then
Err.Clear
Else
' Your other code for non-error case here
End If
i = i + 1
Loop
End Sub

Resources