vba Deleting specific rows and columns in a word table - vba

I need to parse most of a word doc tables and delete the cells containing the string "Deleted". The tables have different formats so i need to change the start index depending on the format.i wrote a code where i start first deleting rows with all cells containing the "Deleted" string. The same strategy is used to delete the columns. It worked for the rows but not for the columns delete. When running through the table cells the column index is bigger than the actual available columns due to deleted columns.i dont know why it occured in the column delete part and not in the row delete one. The code below works for some of the tables but not all of them.
Dim msWord As Word.Application
Dim myDoc As Word.Document
Dim wordTable As Table
Dim r As Long
Dim c As Long
Dim col_del_cnt As Long
Dim row_del_cnt As Long
Dim i As Long
Dim col_index As Long
Dim param As Long
Dim Tbl As Table
Dim Tmpfile As String
Tmpfile = ThisWorkbook.Path & "\Template.docx"
With msWord
.Visible = True
Set myDoc = .Documents.Open(Filename:=Tmpfile)
End With
With myDoc
For i = 7 To 21
Set wordTable = .Tables(i)
If wordTable.Columns.Count < 2 Then
col_index = 1
param = wordTable.Columns.Count
Else
col_index = 2
param = wordTable.Columns.Count - 1
End If
For r = 2 To wordTable.Rows.Count
col_del_cnt = 0
For c = col_index To wordTable.Columns.Count
If InStr(1, wordTable.Cell(r, c).Range.Text, "Deleted", 1) Then
col_del_cnt = col_del_cnt + 1
End If
Next c
If col_del_cnt = param Then
If r > wordTable.Rows.Count Then
wordTable.Rows(wordTable.Rows.Count).Delete
Else
wordTable.Rows(r).Delete
End If
End If
Next r
Next
End With
With myDoc
For i = 7 To 21
Set wordTable = .Tables(i)
If wordTable.Columns.Count < 2 Then
col_index = 1
Else
col_index = 2
End If
For c = col_index To wordTable.Columns.Count
row_del_cnt = 0
For r = 2 To wordTable.Rows.Count
If InStr(1, wordTable.Cell(r, c).Range.Text, "Deleted", 1) Then '\Error located here'
row_del_cnt = row_del_cnt + 1
End If
Next r
If row_del_cnt = wordTable.Rows.Count - 1 Then
If c > wordTable.Columns.Count Then
wordTable.Columns(wordTable.Columns.Count).Delete
Else
wordTable.Columns(c).Delete
End If
End If
Next c
Next
End With
I hope someone could help me to find the solution.

When deleting something indexed, you have to do it backwards.
Change
For i = 7 To 21
to
For i= 21 to 7 Step -1
and so on.

It appears you're trying to delete both the row and the column when a cell has 'Deleted' in it. Obviously, if you use one loop to delete a row that has 'Deleted' in it, then a second loop to delete a column that has 'Deleted' in it, the second loop won't find anything. Try something based on:
Dim t As Long, r As Long, c As Long, ArrCols() As String
With myDoc
For t = 21 To 7 Step -1
With .Tables(t)
If InStr(1, .Range.Text, "Deleted", 1) Then
ReDim ArrCols(.Columns.Count)
For r = .Rows.Count To 1 Step -1
With .Rows(r)
If InStr(1, .Range.Text, "Deleted", 1) Then
For c = 1 To .Cells.Count
If InStr(1, .Cells(c).Range.Text, "Deleted", 1) Then
ArrCols(c) = c
End If
Next
.Delete
End If
End With
Next r
For c = UBound(ArrCols) To 1 Step -1
If ArrCols(c) <> "" Then .Columns(c).Delete
Next
End If
End With
Next
End With
Note how all the loops involving deletions run backwards.
The fact your own code didn't throw errors with the row deletions was just a coincidence.

Related

Delete rows in MS Word if some specific cells are empty

So I have this VBA Code to delete the rows in Word tables if the 6th cells of those tables are empty. But the thing is not every table has 6 columns so this code deletes all my tables with 5 columns or less. Can you guys help me out to add a function that counts the number of columns if it's smaller than 6 then exit sub? Sorry for my bad English.
Sub BCPISADeleteEmptyRows()
Dim tbl As Table, cel As Cell
Dim i As Long, j As Long, n As Long, fEmpty As Boolean
Application.ScreenUpdating = False
With ActiveDocument
For Each tbl In .Tables
n = tbl.Rows.Count
For i = n To 1 Step -1
fEmpty = True
For j = 6 To tbl.Rows(i).Cells.Count
Set cel = tbl.Rows(i).Cells(j)
If Len(Trim(cel.Range.Text)) > 2 Then
fEmpty = False
Exit For
End If
Next j
If fEmpty = True Then tbl.Rows(i).Delete
Next i
Next tbl
End With
Set cel = Nothing: Set tbl = Nothing
Application.ScreenUpdating = True
End Sub
For example:
Sub BCPISADeleteEmptyRows()
Application.ScreenUpdating = False
Dim Tbl As Table, r As Long
For Each Tbl In ActiveDocument.Tables
With Tbl
If .Columns.Count > 5 Then
For r = .Rows.Count To 2 Step -1
If Len(Trim(.Cell(r, 6).Range.Text)) = 2 Then .Rows(r).Delete
Next
End If
End With
Next
Application.ScreenUpdating = True
End Sub

Excel: VBA to delete group of rows

I have an excel file with one column with data. Something like:
21/07/2017
DEF
GHI
Field 7
SOMETHING HERE
MORE TEXT
21/07/2017
DEF
GHI
Field 7
This is repeated a few thousand times. What I am looking for is all rows between and including 21/07/2017 and Field 7 to be deleted and for the rows to be moved up.
I've tried a few things but now back to a blank canvas! Any hints?
Thanks
CODE I TRIED
I get an Overflow error
Sub deleteRows()
Dim sh As Worksheet
Dim rw As Range
Dim RowCount As Integer
RowCount = 1
Application.DisplayAlerts = False
Set sh = ActiveSheet
For Each rw In sh.Rows
If sh.Cells(rw.Row, 1).Value = "21/07/2017" Then
a = RowCount
End If
If sh.Cells(rw.Row, 1).Value = "Field 7" Then
b = RowCount
Rows(a & ":" & b).Delete
End If
RowCount = RowCount + 1
Next rw
End Sub
This will only loop as many times as the pair exists and delete each block as a whole.
The loop ends the first time that both are not found in the remaining values.
Sub myDelete()
Dim str1 As string
Dim str2 As String
Dim rng As Range
Dim ws As Worksheet
Dim i As Long
Dim j As Long
str1 = "21/07/2017"
str2 = "Field 7"
Set ws = Worksheets("Sheet18") 'change to your worksheet
Set rng = ws.Range("A:A")
Do
i = 0: j = 0
On Error Resume Next
i = Application.WorksheetFunction.Match(str1, rng, 0)
j = Application.WorksheetFunction.Match(str2, rng, 0)
On Error GoTo 0
If i > 0 And j > 0 Then
ws.Rows(i & ":" & j).Delete
End If
Loop Until i = 0 Or j = 0
End Sub
If your date is a true date then change str1 to Double:
Dim str1 As Double
and then assign it as such:
str1 = CDbl(DateSerial(2017, 7, 21))

Select limited data row from filtered data

I have 70,000 rows of data in an Excel sheet. After applying a filter, the total number of visible rows becomes 40,000. Now I would like to select and copy the first 15,000 visible rows only.
This should work for you. I would suggest setting it up to run on a keyboard shortcut.
Const limit As Integer = 15000
Sub GrabFiltered()
Dim r As Range
Dim lr As Range
Dim tr As Range
Dim rr As Range
Dim br As Range
Dim table As Range
Dim rows As Integer
Dim i As Integer
Dim ct As Integer
Dim offset As Integer
Set r = Selection.Cells(1, 1)
If r.End(xlToLeft).Cells(1, 1).FormulaR1C1 <> "" Then
Set lr = r.End(xlToLeft).Cells(1, 1)
Else
Set lr = r
End If
If lr.End(xlUp).Cells(1, 1).FormulaR1C1 <> "" Then
Set tr = lr.End(xlUp).Cells(1, 1)
Else
Set tr = lr
End If
If r.End(xlToRight).Cells(1, 1).FormulaR1C1 <> "" Then
Set rr = r.End(xlToRight).Cells(1, 1)
Else
Set rr = r
End If
rr.Select
If rr.End(xlDown).Cells(1, 1).FormulaR1C1 <> "" Then
Set br = rr.End(xlDown).Cells(1, 1)
Else
Set br = r
End If
Set table = Range(tr, br)
'count the number of rows that are visible
rows = 0
For i = 1 To table.rows.Count
If table.Cells(i, 1).Height <> 0 Then
rows = rows + 1
End If
Next
'limit the number of rows to copy
If rows > limit Then
offset = rows - limit
i = 1
ct = 1
While i <> offset
If br.offset(-ct, 0).Height <> 0 Then
i = i + 1
End If
ct = ct + 1
Wend
Set br = br.offset(-ct, 0)
Set table = Range(tr, br)
End If
table.Copy
End Sub

Need help improving my VBA loop

I have an Excel Worksheet consisting of two columns, one of which is filled with strings and the other is emtpy. I would like to use VBA to assign the value of the cells in the empty column based on the value of the adjacent string in the other column.
I have the following code:
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
Dim j As Integer
For j = 1 To 2
If regexAdmin.test(Cells(i, j).Value) Then
Cells(i, j + 1).Value = "Exploitation"
End If
Next j
Next i
The problem is that when using this loop for a big amount of data, it takes way too long to work and, most of the time, it simply crashes Excel.
Anyone knows a better way to this?
You have an unnecessary loop, where you test the just completed column (j) too. Dropping that should improve the speed by 10-50%
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
If regexAdmin.test(Cells(i, 1).Value) Then
Cells(i, 1).offset(0,1).Value = "Exploitation"
End If
Next i
If the regex pattern really is simply "Admin", then you could also just use a worksheet formula for this, instead of writing a macro. The formula, which you'd place next to the text column (assuming your string/num col is A) would be:
=IF(NOT(ISERR(FIND("Admin",A1))),"Exploitation","")
In general, if it can be done with a formula, then you'd be better off doing it so. it's easier to maintain.
Try this:
Public Sub ProcessUsers()
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim r As Range, N As Integer, i As Integer
Set r = Range("A1") '1st row is headers
N = CountRows(r) - 1 'Count data rows
Dim inputs() As Variant, outputs() As Variant
inputs = r.Offset(1, 0).Resize(N, 1) ' Get all rows and 1 columns
ReDim outputs(1 To N, 1 To 1)
For i = 1 To N
If regexAdmin.test(inputs(i, 1)) Then
outputs(i, 1) = "Exploitation"
End If
Next i
'Output values
r.Offset(1, 1).Resize(N, 1).Value = outputs
End Sub
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End If
End Function

Merge Excel Sheets Using VBA

I have a Excel Sheet(Say OG.xls) which has some data already in it with some 5000 rows with headings in the first row and Upto "AN" Columns.
This No of rows(5000) doesn't change for a whole year.
Now i have 5 XL files(Say A,B,C,D,E) and the data from these files has to be appended to this OG file just starting from 5001st row every time.
All these 5 files has different no of columns but identical to that of OG File.
I have to pull data from these files and place them in OG File.
From File A : Column A,B,C,D,E,F,G&H goes to Column F,G,T,U,V,W,X&Y Of OG.xls File.
Likewise the other files data has to be extracted according to the corresponding column with OG.xls
The second file data has to be appended right below the next row where the File A ends.(Say after filling the data from File A now the OG.xls has 5110 rows,
the File B data has to filled from 5111 st row of OG.xls.
The same follows for the other files too.
The data of these 5 files has to be filled row after row but should match the columns to that of OG.xls
Each time the same operation is repeated by filling the data from 5001st row of OG.xls. For convenience we can have all these files in a same folder.
How can we do this.
Please help me in this!!!
Also let me know for any clarifications.
If you need a more presice answer, you would need to try something first and then ask for help in area you have got stuck. My suggestion is you begin by;
1. Start writing a VBA script in OG.XLS, as a first step try to access the file A.xls and reading the columns and pasting them (they can initially be at any location in any order).
2. Once you are able to do this, next step is to see if you put the data in right column (say 5000 in your example) by setting up right kind of variables and using them and incrementing them.
3. Your next step should be to to read the column headings in A.XLS and finding them OG.XLS and identifying them. Initially you can begin by doing a simple string comparision, later you can refine this to do a VLOOKUP.
4. During this process, if you encounter any specific problem, raise it so that you will get a better answer.
Few from the community would go to the extent of writing the entire code for you.
Why does Column A end up in Column F, and why does C end up in T? Is there a rule around this such as the first row is a header with with the same text in it?
Maybe a picture might help.
Based on what i can guess, i'd throw each sheet into a RecordSet with meaningful field names (you'll need to reference Microsoft ActiveX Data Objects 2.8 Library) . Once done it will be very easy to append each RecordSet and throw them into a single sheet.
You'll need to be able to find the last column and last row in each sheet to do this cleanly so have a look at How can i find the last row...
Edit...
Below is a cleaned up example of how you could do what you need in VBA. The devil is in the details such as empty sheets, and how to handle formulas (this ignores them completely), and how to merge you columns in an appropriate way (again ignored).
This has been tested in Excel 2007.
Option Explicit
Const MAX_CHARS = 1200
Sub MergeAllSheets()
Dim rs As Recordset
Dim mergedRS As Recordset
Dim sh As Worksheet
Dim wb As Workbook
Dim fieldList As New Collection
Dim rsetList As New Collection
Dim f As Variant
Dim cols As Long
Dim rows As Long
Dim c As Long
Dim r As Long
Dim ref As String
Dim fldName As String
Dim sourceColumn As String
Set wb = ActiveWorkbook
For Each sh In wb.Worksheets
Set rs = New Recordset
ref = FindEndCell(sh)
cols = sh.Range(ref).Column
rows = sh.Range(ref).Row
If ref <> "$A$1" Or sh.Range(ref).Value <> "" Then '' This is to catch empty sheet
c = 1
r = 1
Do While c <= cols
fldName = sh.Cells(r, c).Value
rs.Fields.Append fldName, adVarChar, MAX_CHARS
If Not InCollection(fieldList, fldName) Then
fieldList.Add fldName, fldName
End If
c = c + 1
Loop
rs.Open
r = 2
Do While r <= rows
rs.AddNew
c = 1
Do While c <= cols
rs.Fields(c - 1) = CStr(sh.Cells(r, c).Value)
c = c + 1
Loop
r = r + 1
Debug.Print sh.Name & ": " & r & " of " & rows & ", " & c & " of " & cols
Loop
rsetList.Add rs, sh.Name
End If
Next
Set mergedRS = New Recordset
c = 1
sourceColumn = "SourceSheet"
Do While InCollection(fieldList, sourceColumn) '' Just in case you merge a merged sheet
sourceColumn = "SourceSheet" & c
c = c + 1
Loop
mergedRS.Fields.Append sourceColumn, adVarChar, MAX_CHARS
For Each f In fieldList
mergedRS.Fields.Append CStr(f), adVarChar, MAX_CHARS
Next
mergedRS.Open
c = 1
For Each rs In rsetList
If rs.RecordCount >= 1 Then
rs.MoveFirst
Do Until rs.EOF
mergedRS.AddNew
mergedRS.Fields(sourceColumn) = "Sheet No. " & c
For Each f In rs.Fields
mergedRS.Fields(f.Name) = f.Value
Next
rs.MoveNext
Loop
End If
c = c + 1
Next
Set sh = wb.Worksheets.Add
mergedRS.MoveFirst
r = 1
c = 1
For Each f In mergedRS.Fields
sh.Cells(r, c).Formula = f.Name
c = c + 1
Next
r = 2
Do Until mergedRS.EOF
c = 1
For Each f In mergedRS.Fields
sh.Cells(r, c).Value = f.Value
c = c + 1
Next
r = r + 1
mergedRS.MoveNext
Loop
End Sub
Public Function InCollection(col As Collection, key As String) As Boolean
Dim var As Variant
Dim errNumber As Long
InCollection = False
Set var = Nothing
Err.Clear
On Error Resume Next
var = col.Item(key)
errNumber = CLng(Err.Number)
On Error GoTo 0
'5 is not in, 0 and 438 represent incollection
If errNumber = 5 Then ' it is 5 if not in collection
InCollection = False
Else
InCollection = True
End If
End Function
Public Function FindEndCell(sh As Worksheet) As String
Dim cols As Long
Dim rows As Long
Dim maxCols As Long
Dim maxRows As Long
Dim c As Long
Dim r As Long
maxRows = sh.rows.Count
maxCols = sh.Columns.Count
cols = sh.Range("A1").End(xlToRight).Column
If cols >= maxCols Then
cols = 1
End If
c = 1
Do While c <= cols
r = sh.Cells(1, c).End(xlDown).Row
If r >= maxRows Then
r = 1
End If
If r > rows Then
rows = r
End If
c = c + 1
Loop
FindEndCell = sh.Cells(rows, cols).Address
End Function