How do I delete paragraphs of a certain language? - vba

I wish to delete Simplified Chinese text in a document with both English and Chinese. The documents don't have any set pattern for which paragraphs are in which language.
I tried a few versions of code that search by paragraph and by language.
Sub DeleteCN()
iParCount = ActiveDocument.Paragraphs.Count
For J = 1 To iParCount
sMyPar = ActiveDocument.Paragraphs(J).Range.Text
If sMyPar.WdLanguageID = wdSimplifiedChinese Then
sMyPar.Delete
End If
Next J
End Sub
The error I get with this latest attempt is that an object is required on the If line.

You have a few issues with your code.
1) The most serious is you must reverse your loop. The loop must be reversed because as you delete a paragraph the number of paragraphs will dynamically change and then future paragraphs will no longer exist.
2) The rest are syntax errors, you can see where the syntax has been updated in the code. If you declare your variables it will be easier to know the correct syntax.
Sub DeleteCN()
Dim iParaCount As Integer
Dim para As Paragraph
iParaCount = ActiveDocument.Paragraphs.Count
For J = iParaCount To 1 Step -1
Set para = ActiveDocument.Paragraphs(J)
If para.Range.LanguageID = wdSimplifiedChinese Then
para.Range.Delete
End If
Next J
End Sub
Hope this helps.

Related

Is it possible to get to the end of the list in vba?

I am wondering if it is possible to get to the end of the list in vba? For example, I have a document with manual numbering and autonumbering in word.
Now, I would like to apply styles. But, when applying styles to auto - numbered list the numbering would be removed upon applying another style. So, to overcome this problem I am wondering if it is possible to get to the end of the list. So, that I would convert autonumbering into manual number and apply the formatting.
Sub applyformatting()
pos2 = Selection.Range.End
pos1 = Selection.Range.Start
Dim i As Integer, para As Paragraph
For i = 1 To ActiveDocument.Range.Paragraphs.Count
Set para = ActiveDocument.Range.Paragraphs(i)
If para.Range.ListFormat.ListType <> wdListBullet Or para.Range.ListFormat.ListType <> wdListSimpleNumbering Then
' Goto the end of the list and do the following until it reaches current paragraphs
Do Until Selection.Range.Start = pos1
Selection.MoveUp wdParagraph, 1
para.Range.ListFormat.ConvertNumbersToText
para.Range.Style = "tt"
Loop
Else
para.Range.Style = "t"
End If
Next
End Sub

Use Word Macro to Determine last character of Paragraph

I have been using this code to Bold-Underline all the headers in my word doc:
Sub Underline_Headers()
Dim p As Paragraph
For Each p In ActiveDocument.Paragraphs
If Len(p.Range.Text) < 70 Then
p.Range.Font.Underline = True
p.Range.Font.Bold = True
End If
Next p
End Sub
This works great - as long as every header is less than 70 characters long, and the paragraph underneath it is 70 or more characters.
But many times the header can be longer than 70 characters, and the paragraph under the header can be less than 70 characters.
However, the headers always never end with any punctuation, like a "." but the paragraphs underneath them always do.
I am trying to fix the code above to look for all paragraphs not ending in a "." and then Bold-Underline them. In other words, I want to change the rule.
I tried the only thing that made sense to me. The code did not break, but it ended up bold-underline the entire document:
Sub Underline_Headers()
Dim p As Paragraph
For Each p In ActiveDocument.Paragraphs
If Right(p.Range.Text,1) <> "." Then
p.Range.Font.Underline = True
p.Range.Font.Bold = True
End If
Next p
End Sub
This supposedly looks for all paragraphs where the last character is not ".", which if that worked, would isolate all the headers and only bold-underline them, but obviously that doesn't work.
The last character in every paragraph is a carriage return, Chr(13). The text ends one character before that. The code below also considers the possibility that someone ended a paragraph's text with one or more blank spaces. It takes the "cleaned" string and looks for the last character in a string of possible exceptions, like .?!. You can reduce this string to a single full stop or extend it to include more cnadidates for exception.
Private Sub UnderlineTitles()
Dim Para As Paragraph
Dim Txt As String
Application.ScreenUpdating = False
For Each Para In ActiveDocument.Paragraphs
Txt = Para.Range.Text
Txt = RTrim(Left(Txt, Len(Txt) - 1))
' you can extend the list to include characters like ")]}"
If InStr(".?!", Right(Txt, 1)) = 0 Then
' to choose a different style of underline, remove
' "= wdUnderlineSingle", type "=" and select from the dropdown
Para.Range.Font.Underline = wdUnderlineSingle
End If
Next Para
Application.ScreenUpdating = True
End Sub

VBA Word make every 3 words' bold in a selection

So I have been trying to make every 3 words in a word docuemnt bold in a specific selection or if there is nothing selected every 3 words in the whole document. I tried different approaches but nothing worked.
I should say "What have you tried so far?" and "Lets see your code.", but I haven't really coded in Word so thought I'd give it a go....
This seems to do the trick, although there may be a much better way to code it:
Public Sub BoldText()
Dim wrd As Range
Dim x As Long
Dim doc As Variant
If Selection.Start = Selection.End Then
Set doc = ThisDocument
Else
Set doc = Selection
End If
x = 0
For Each wrd In doc.Words
x = x + 1
If x Mod 3 = 0 Then
wrd.Bold = True
End If
Next wrd
End Sub

Read a table in outlook mail using macro

I'm writing a macro to read the below Email:
Start Date: July-07-2016
Name Accept Approved
John Yes No
Peter No No
I'm good with search the word "Start date" and get the next 13 character to copy and paste that in a text file. But my problem is the next part is in a Table format. So when I'm searching for the name "John" and trying to copy the next 10 Characters. It doesn't work.
Is there a way to search for the word "Accept" and get the First Row data(Which will be No) and then Second Row data(Which will be No)? Is that possible?
This EMail's table will have only 2 Rows. So, I don't need any dynamic way to get the data. Can someone guide me?
I've tried searching the internet first, but the solutions are too huge for me to understand. Is there any simple way?
I have even tried the solution give here: How to read table pasted in outlook message body using vba? but that method works when the body has ONLY TABLE. But my EMail will have text as well as table.
I've never actually programmed in vba, but I think I can help (a bit) nevertheless.
In the answer on the post you linked to, there is the line
Set msg = ActiveExplorer.Selection.item(1)
I think you can change this to something like
Set msg = Right(ActiveExplorer.Selection.item(1), 25)
to get rid of the text before the table (I got the Right part from here: http://www.exceltrick.com/formulas_macros/vba-substring-function/, but it should also work in Outlook).
This way, you run the code on the table itself instead of on the whole message.If there is also text after the table, it might be more difficult, but you might get that done by searching for the table ending.
I hope this helps!
Attempt 2
After some searching and thinking, I came up with the idea to get the html of the message and use that to parse the table (Ok, not really, I got it from the comments here: http://www.codeproject.com/Questions/567073/Howplustoplusrecognizeplusandplusreadplustableplus). Based on that and other sources, it is possible to write a code that gets the table from an email.
I've written some code that might work, but I couldn't test it as I do not have Outlook. Also, this is my first time writing vba, so there may be a lot of syntax errors (and the code is ugly).
Sub GetTable()
Dim msg As Outlook.mailItem
Dim html As String
Dim tableBegin As String
Dim tableEnd As String
Dim posTableBegin As Long
Dim posTableEnd As Long
Dim table As String
Dim rowBegin As String
Dim rowEnd As String
Dim rowCount As Long
Dim columnBegin As String
Dim columnBeginLen As Long
Dim columnEnd As String
Dim posRowBegin As Long
Dim posRowEnd As Long
Dim values As String(0, 3)
Dim beginValue0 As Long
Dim beginValue1 As Long
Dim beginValue2 As Long
Dim EndValue0 As Long
Dim EndValue1 As Long
Dim EndValue2 As Long
' Get the message and the html
Set msg = ActiveExplorer.Selection.item(1)
html = msg.HTMLbody
' Get the begin and end positions of the table (within the html)
tableBegin = "<table>"
tableEnd = "</table>"
posTableBegin = InStr(1, html, tableBegin)
posTableEnd = InStr(posTableBegin, html, tableEnd)
' Get the html table
table = Mid(html, posTableBegin + Len(tableBegin), posTableEnd - posTableBegin - Len(tableBegin))
' Set the variables for the loop
rowBegin = "<tr>"
rowEnd = "</tr>"
rowCount = 0
columnBegin = "<td>"
columnBeginLen = Len(columnBegin)
columnEnd = "</td>"
' Loop trough all rows
posRowBegin = InStr(lastPos, table, rowBegin)
Do While posRowBegin != 0
' Get the end from the current row
posRowEnd = InStr(posRowBegin, table, rowEnd)
rowCount = rowCount + 1
' Make the array larger
ReDim Preserve values(rowCount + 1, 3)
' Get the contents from that row
row = Mid(table, posRowBegin + Len(rowBegin), posRowEnd - posRowBegin - Len(rowBegin))
' Get the three values from that row (name, Accept, Approved) and put it in the array
beginValue0 = InStr(1, row, columnBegin) + columnBeginLen
endValue0 = InStr(beginValue0, row, columnEnd)
beginValue1 = InStr(endValue0, row, columnBegin) + columnBeginLen
endValue1 = InStr(beginValue1, row, columnEnd)
beginValue2 = InStr(endValue1, row, columnBegin) + columnBeginLen
endValue2 = InStr(beginValue2, row, columnEnd)
values(rowCount, 0) = Mid(row, beginValue0, endValue0)
values(rowCount, 1) = Mid(row, beginValue1, endValue1)
values(rowCount, 2) = Mid(row, beginValue2, endValue2)
' Get the beginning of the next row
posRowBegin = InStr(lastPos, table, rowBegin)
Loop
' The values are now in the (double) array 'values'.
' values(0, [1-3]) contains the headers.
End Sub
As said before, the original idea came from http://www.codeproject.com/Questions/567073/Howplustoplusrecognizeplusandplusreadplustableplus. Additionally, I used Word VBA how to select text between two substrings and assign to variable? and the Microsoft documentation to write this.
While it is likely that the code does not work out of the box, I think it still gets the general idea (and some specifics) across, so that it can be used as a guide. I hope this is the solution you need!
You can actually use the Word Object Model to parse out the text from the table - assuming that the email is in HTML format.
Get a Word.Document object from the Inspector.WordEditor property and use Word objects and methods to get the text, like the following below example from MSDN. Just replace ActiveDocument with the variable you declare and set from WordEditor.
Sub ReturnCellContentsToArray()
Dim intCells As Integer
Dim celTable As Cell
Dim strCells() As String
Dim intCount As Integer
Dim rngText As Range
If ActiveDocument.Tables.Count >= 1 Then
With ActiveDocument.Tables(1).Range
intCells = .Cells.Count
ReDim strCells(intCells)
intCount = 1
For Each celTable In .Cells
Set rngText = celTable.Range
rngText.MoveEnd Unit:=wdCharacter, Count:=-1
strCells(intCount) = rngText
intCount = intCount + 1
Next celTable
End With
End If
End Sub

Keeping a count in a dictionary, bad result when running the code, good result adding inspections

Weird problem. Stepping through the code with inspections gives me correct answers. Just running it doesn't.
This program loops through each cell in a column, searching for a regex match. When it finds something, checks in a adjacent column to which group it belongs and keeps a count in a dictonary. Ex: Group3:7, Group5: 2, Group3:8
Just stepping through the code gives me incorrect results at the end, but adding and inspection for each known item in the dictionary does the trick. Using Debug.Print for each Dictionary(key) to check how many items I got in each loop also gives me a good output.
Correct // What really hapens after running the code
Group1:23 // Group1:23
Group3:21 // Group3:22
Group6:2 // Group6:2
Group7:3 // Group7:6
Group9:8 // Group9:8
Group11:1 // Group11:12
Group12:2 // Group12:21
Sub Proce()
Dim regEx As New VBScript_RegExp_55.RegExp
Dim matches
Dim Rango, RangoJulio, RangoAgosto As String
Dim DictContador As New Scripting.Dictionary
Dim j As Integer
Dim conteo As Integer
Dim Especialidad As String
regEx.Pattern = "cop|col"
regEx.Global = False 'True matches all occurances, False matches the first occurance
regEx.IgnoreCase = True
i = 3
conteo = 1
RangoJulio = "L3:L283"
RangoAgosto = "L3:L315"
Julio = Excel.ActiveWorkbook.Sheets("Julio")
Rango = RangoJulio
Julio.Activate
For Each celda In Julio.Range(Rango)
If regEx.Test(celda.Value) Then
Set matches = regEx.Execute(celda.Value)
For Each Match In matches
j = 13 'column M
Especialidad = Julio.Cells(i, j).Value
If (Not DictContador.Exists(Especialidad)) Then
Call DictContador.Add(Especialidad, conteo)
GoTo ContinueLoop
End If
conteo = DictContador(Especialidad)
conteo = CInt(conteo) + 1
DictContador(Especialidad) = conteo
Next
End If
ContinueLoop:
i = i + 1
'Debug.Print DictContador(key1)
'Debug.Print DictContador(key2)
'etc
Next
'Finally, write the results in another sheet.
End Sub
It's like VBA saying "I'm going to dupe you if I got a chance"
Thanks
Seems like your main loop can be reduced to this:
For Each celda In Julio.Range(Rango)
If regEx.Test(celda.Value) Then
Especialidad = celda.EntireRow.Cells(13).Value
'make sure the key exists: set initial count=0
If (Not DictContador.Exists(Especialidad)) Then _
DictContador.Add Especialidad, 0
'increment the count
DictContador(Especialidad) = DictContador(Especialidad) +1
End If
Next
You're getting different results stepping through the code because there's a bug/feature with dictionaries that if you inspect items using the watch or immediate window the items will be created if they don't already exist.
To see this put a break point at the first line under the variable declarations, press F5 to run to the break point, then in the immediate window type set DictContador = new Dictionary so the dictionary is initialised empty and add a watch for DictContador("a"). You will see "a" added as an item in the locals window.
Collections offer an alternative method that don't have this issue, they also show values rather than keys which may be more useful for debugging. On the other hand an Exists method is lacking so you would either need to add on error resume next and test for errors instead or add a custom collection class with an exists method added. There are trade-offs with both approaches.