Word VBA Move Images and Text into a Table - vba

I'm trying to check every section of a document for images or grouped images and, if they're found, create a table with 1 row and 2 columns at the beginning of that section, where the first column will contain the text (with original formatting) and the second column will contain the images. I have converted all the images in the document to inline shapes.
Edit: In the document, there's random amounts of text (and/or other characters) before, after and in between a random amount of images. Sometimes a section has no text and only images. For each section, I would like all of the text (with original formatting and in the order in which it occurs) to be contained in the first column, and all images and grouped images (also in their same order) to be contained in the second folder. Ideally, if the only things on the page are a heading and an image, they would be put into a 1x1 table (with the heading above the image).
I've tried a few variations of this with no success. Generally stuff starts getting pretty messy because I have no idea what I'm doing. I've left out the text in this code because it was only complicating things, but I would like to move the text as well.
Sub ToTables()
Dim iShp As InlineShape
Dim oRng As Range
Dim oTbl As Table
Dim i As Integer
Dim a As Integer
Dim b As Integer
a = ActiveDocument.BuiltInDocumentProperties("Number of Sections")
For i = 1 To a
Set oRng = ActiveDocument.GoTo(What:=wdGoToSection, Name:=i)
Set oRng = oRng.GoTo(What:=wdGoToBookmark, Name:="\section")
If Right(oRng, 1) = vbCr Then _
oRng = Left(oRng, Len(oRng) - 1)
b = oRng.InlineShapes.Count
If b >= 1 Then
oRng.Collapse Direction:=wdCollapseStart
Set oTbl = oRng.Tables.Add(oRng, 1, 2, AutoFitBehavior:=wdAutoFitContent)
For Each iShp In oRng.InlineShapes
iShp.Select
Selection.Cut
oTbl.Cell(1, 2).Range.Paste
Next iShp
End If
Next i
End Sub
Thanks

Try the revised code:
Sub Demo()
Application.ScreenUpdating = False
Dim Sctn As Section, Rng As Range, Tbl As Table, s As Long, w As Single
For Each Sctn In ActiveDocument.Sections
Set Rng = Sctn.Range: w = 0
Rng.End = Rng.End - 1
Set Tbl = Rng.ConvertToTable(, NumRows:=1, NumColumns:=1, InitialColumnWidth:=50, AutoFit:=True)
With Tbl
.Columns.Add
For s = .Range.InlineShapes.Count To 1 Step -1
With .Range.InlineShapes(s)
If .Width > w Then w = .Width
.Range.Rows(1).Cells(2).Range.FormattedText = .Range.FormattedText
.Delete
End With
Next
.Columns(1).Cells.Merge
.Columns(2).Cells.Merge
.PreferredWidthType = wdPreferredWidthPercent
.PreferredWidth = 100
If w > 0 Then .Columns(2).Width = w + .LeftPadding + .RightPadding
.Rows.HeightRule = wdRowHeightAuto
End With
Next
Application.ScreenUpdating = True
End Sub

Assuming the text precedes the inlineshapes:
Sub Demo()
Application.ScreenUpdating = False
Dim iShp As InlineShape
For Each iShp In ActiveDocument.InlineShapes
With iShp.Range
.Characters.First.Previous = vbTab
.Start = .Paragraphs.First.Range.Start
.ConvertToTable vbTab, 1, 2
End With
Next
Application.ScreenUpdating = True
End Sub
The above code assumes there is a single character between the text & inlineshape. That character could be a space, paragraph break, line break, anything at all.

Related

How to clean a Word table before saving to a Word bookmark?

I am writing Word VBA that:
(1) assigns values from a Word table to VBA variables,
(2) cleans the variables' values of non-text,
(3) uses the variables' names and values to create Bookmarks in that same bookmark_value cell of the table, and
(4) repeats 1-2-3 until the end of table.
This table is the first table in the document and has two columns, something like this:
_________________________________
| bookmark_name | bookmark_value|
| bm1 | 88 |
| foo | 66 |
|_____bar_______|______44_______|
The code picks up the bookmark_names and posts into Word Bookmarks, and also picks up the bookmark_values but fails to clean the table coding out of the value.
The result is the Bookmarks displaying these unwanted cells in Word with the value inside it. It is strange that first column works and not the second.
Some things I tried:
I found on the Internet and on this site, what I thought were solutions, those are marked in the code below with comments, the header saying, "tried and failed".
I am nearly sure I need to "unformat" the text, or something like that.
Public Sub BookmarkTable()
Dim selectedTable As Table
Dim curRow As Range
Dim rngSelect1 As Range
Dim rngSelect2 As Range
Dim intTableIndex As Integer
Dim rng As Range
Dim Cell1 As Cell, Cell2 As Cell
Dim strBookmarkName As String, strBookmarkValue As String, strBV As String
Dim strTstBookmark As String
Dim Col1 As Integer, Col2 As Integer
Dim i As Integer, t As Integer
Dim intRow As Integer
' Dim
Col1 = 1 'set the bookmark name from column 1
Col2 = 2 'set the bookmark's value from column 2
'For t = 1 To ActiveDocument.Tables.Count
t = 1 'select the Table to use(only using the first table right now)
Set selectedTable = ActiveDocument.Tables(t)
selectedTable.Select 'selects the table
For intRow = 2 To selectedTable.Rows.Count 'iterate through all rows
If Selection.Information(wdWithInTable) Then
Set Cell1 = ActiveDocument.Tables(t).Cell(intRow, Col1)
Set Cell2 = ActiveDocument.Tables(t).Cell(intRow, Col2)
Cell2.Select
intTableIndex = ActiveDocument.Range(0, Selection.Tables(1).Range.End).Tables.Count
rngColumnStart = Selection.Information(wdStartOfRangeColumnNumber)
rngRowStart = Selection.Information(wdStartOfRangeRowNumber)
End If
strTstBookmark = "BM_Table" & CStr(intTableIndex) & "_R" & CStr(rngRowStart) & "_C" & CStr(rngColumnStart)
' strBookmarkValue = strTstBookmark
Set rngSelect1 = ActiveDocument.Range(Start:=Cell1.Range.Start, End:=Cell1.Range.End - 1)
strBookmarkName = Strip(rngSelect1.Text)
Set rngSelect2 = ActiveDocument.Range(Start:=Cell2.Range.Start, End:=Cell2.Range.End - 1)
strBookmarkValue = Strip(rngSelect2.Text)
Set rng = ActiveDocument.Tables(intTableIndex).Cell(rngRowStart, rngColumnStart).Range
rng.End = rng.End - 1
'--------------------------------------------------------------------------
'tried and failed)
'--------------------------------------------------------------------------
'Stop
If ActiveDocument.Bookmarks.Exists(strBookmarkName) = True Then
ActiveDocument.Bookmarks(strBookmarkName).Delete
End If
If ActiveDocument.Bookmarks.Exists(strTstBookmark) = True Then
ActiveDocument.Bookmark(strTstBookmark).Delete
End If
ActiveDocument.Bookmarks.Add Name:=strTstBookmark
ActiveDocument.Bookmarks.Add Name:=strBookmarkName
ActiveDocument.Bookmarks(strBookmarkName).Range.Text = strBookmarkValue
Next intRow
'Next t
End Sub
'--------------------------------------------------------------------------
'tried and failed
Private Function Strip(ByVal fullest As String)
' fuller = Left(fullest, Len(s) - 2)
Strip = Trim(Replace(fullest, vbCr & Chr(7), ""))
End Function
'--------------------------------------------------------------------------
That's truly horrible code you're using. Try:
Sub BkMkDemo()
Application.ScreenUpdating = False
Dim r As Long, BkMkNm As String, BkMkTxt As String
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count
BkMkNm = Split(.Tables(1).Cell(r, 1).Range.Text, vbCr)(0)
BkMkTxt = Split(.Tables(1).Cell(r, 2).Range.Text, vbCr)(0)
If Not .Bookmarks.Exists(BkMkNm) Then .Bookmarks.Add BkMkNm, .Range.Characters.Last
Call UpdateBookmark(BkMkNm, BkMkTxt)
Next
End With
Application.ScreenUpdating = True
End Sub
Sub UpdateBookmark(BkMkNm As String, BkMkTxt As String)
Dim BkMkRng As Range
With ActiveDocument
If .Bookmarks.Exists(BkMkNm) Then
Set BkMkRng = .Bookmarks(BkMkNm).Range
BkMkRng.Text = BkMkTxt
.Bookmarks.Add BkMkNm, BkMkRng
End If
End With
Set BkMkRng = Nothing
End Sub
If all you want to do is to apply the bookmark to the content of the second cell, you need nothing more complex than:
Sub BkMkDemo()
Application.ScreenUpdating = False
Dim r As Long, BkMkNm As String, BkMkRng As Range
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count
BkMkNm = Split(.Tables(1).Cell(r, 1).Range.Text, vbCr)(0)
Set BkMkRng = .Tables(1).Cell(r, 2).Range
BkMkRng.End = BkMkRng.End - 1
.Bookmarks.Add BkMkNm, BkMkRng
Next
End With
Application.ScreenUpdating = True
End Sub
After a great deal of research and learning by this VBA neophyte, here is the solution that I finally got to work. I found the fix by accident on the Windows Dev Center at msdn dot microsoft dot com posted by Cindy Meister...thank you. Turns out there are a combination of three characters needing to be cleaned when extracting text from a Word table cell: Chr(10) & Chr(13), Chr(11).
I simplified the code using the suggestions of macropod above. Thank you.
Sub aBookmarkTable()
'
'a subroutine compiled by Steven McCrary from various sources
'on the Internet, to use values in the second column of the
'first table in a Word document to create Bookmarks in that second
'column, in place of the value input there.
'
'To use the macros, modify the values in the table and run the macro.
'Then place Field Code references in Word to use the Bookmarks.
'The Bookmarks can be seen through Word menu: Insert>Links>Bookmark
'
'The table has just two columns, looking something like this:
'_________________________________
'| bookmark_name | bookmark_value|
'| bm1 | 88 |
'| foo | 66 |
'|_____bar_______|______44_______|
'
'The code places each Bookmark in the second column of each row, using
'the name given in the first column.
'
'The two critical functions of the macro occur in these two lines of code:
' rngBM.End = rngBM.End - 1
' Strip = Replace(fullest, Chr(10) & Chr(13), Chr(11))
'
' both are explained below where they are used.
Application.ScreenUpdating = False
Dim rng1 As Range, rng2 As Range, rngBM As Range
Dim Cell_1 As Cell, Cell_2 As Cell
Dim strBMName As String, strBMValue As String
Dim r As Integer
Call RemoveBookmarks 'removing bookmarks helped to simlify the coding
With ActiveDocument
For r = 2 To .Tables(1).Rows.Count 'iterate through all rows
Set Cell_1 = ActiveDocument.Tables(1).Cell(r, 1)
Set Cell_2 = ActiveDocument.Tables(1).Cell(r, 2)
Cell_2.Select
Set rng1 = .Range(Cell_1.Range.Start, Cell_1.Range.End - 1)
strBMName = Strip(rng1.Text)
Set rng2 = .Range(Cell_2.Range.Start, Cell_2.Range.End - 1)
Set rngBM = ActiveDocument.Tables(1).Cell(r, 2).Range
'When using data contained in a cell of a Word table,
'grabbing the cell's contents also grabs several other
'characters, which therefore need removed in two steps.
'
'The first step is to clean the extra characters from the text.
strBMValue = Strip(rng2.Text)
'
'The second step is to decrease the range size to put in the
'Bookmark.
rngBM.End = rngBM.End - 1
rngBM.Text = strBMValue
.Bookmarks.Add strBMName, rngBM
Next r
End With
Application.ScreenUpdating = True
Selection.WholeStory
ActiveDocument.Fields.Update
End Sub
Sub RemoveBookmarks()
Dim bkm As Bookmark
For Each bkm In ActiveDocument.Bookmarks
bkm.Delete
Next bkm
End Sub
Private Function Strip(ByVal fullest As String)
' the next line of code is the tricky part of the clean
' process because of how Word formats tables and text
' ASCII code Chr(10) is Line Feed
' Chr(13) is Carriage Return
' Chr(13) + Chr(10): vbCrLf or vbNewLine New line character
' Chr (11) is Vertical Tab, but per Word VBA Manual -
' manual line break (Shift + Enter)
'
Strip = Replace(fullest, Chr(10) & Chr(13), Chr(11))
End Function
Thank you again.
SWM

Keep Picture Ratio in Word File

I add a picture via VBA in the Left Header Cell of a Word document - works fine with the following code. Now I want to keep the ratio of the Picture but want to change the size and I don't know how to do it:
Sub AutoOpen()
Dim dd1 As Document: Set dd1 = ActiveDocument
Dim rng1 As Range, seC As Section, an(2) As Long
Dim rngO As Range, rngAN As Range
Dim strToPict As String
For Each rngO In dd1.StoryRanges
ActiveDocument.ActiveWindow.ActivePane.View.SeekView = wdSeekCurrentPageHeader
If rngO.StoryType = wdEvenPagesHeaderStory Then
Set rng1 = rngO.Duplicate
For Each seC In rng1.Sections
an(0) = seC.Headers(1).Range.InlineShapes(1).Height
an(1) = seC.Headers(1).Range.InlineShapes(1).Width
Set rngAN = seC.Headers(1).Range.InlineShapes(1).Range.Duplicate
seC.Headers(1).Range.InlineShapes(1).Delete
seC.Headers(1).Range.InlineShapes.AddPicture FileName:=strToPict, _
LinkToFile:=False, SaveWithDocument:=True, Range:=rngAN
With seC.Headers(1).Range.InlineShapes(1)
.Height = 50
.LockAspectRatio = True
End With
Next
Dim i As Long
ActiveDocument.Save
'Footer changing'
For i = 1 To ActiveDocument.Sections.Count
With ActiveDocument.Sections(i)
.Footers(wdHeaderFooterPrimary).Range.Text = ActiveDocument.Name + "Text"
End With
Next
End If
Next
End Sub
EDIT: I post the whole code of the Makro.
«I add a picture via VBA in the Left Header Cell of a Word document». There is no such thing as a 'Left Header Cell' in a Word document. The only headers (and footers) Word has are Primary, First Page and Even Pages.
And, as Timothy said, you "really need to learn to use the tools at you fingertips". Moreover, having found LockAspectRatio, a simple web search - if that was really necessary - would show you how to use it.
In any event, since all it seems you're trying to do is to resize the inlineshape and repeat the primary page header, you could use something along the lines of:
Sub AutoOpen()
Application.ScreenUpdating = False
Dim Rng As Range, iShp As InlineShape, Sctn As Section, StrNm As String
With Dialogs(wdDialogInsertPicture)
.Display
StrNm = .Name
End With
With ActiveDocument
If StrNm <> "" Then
Set Rng = .Sections.First.Headers(wdHeaderFooterPrimary).Range.Tables(1).Cell(1, 1).Range
Set iShp = .InlineShapes.AddPicture(FileName:=StrNm, _
LinkToFile:=False, SaveWithDocument:=True, Range:=Rng)
With iShp
.LockAspectRatio = True
.Height = 50
End With
End If
Set Rng = .Sections.First.Footers(wdHeaderFooterPrimary).Range
.Fields.Add Range:=Rng, Type:=wdFieldEmpty, Text:="FILENAME", PreserveFormatting:=False
Rng.InsertAfter vbTab & "Text"
For Each Sctn In .Sections
Sctn.Headers(wdHeaderFooterPrimary).LinkToPrevious = True
Sctn.Footers(wdHeaderFooterPrimary).LinkToPrevious = True
Next
End With
Application.ScreenUpdating = True
End Sub
You really need to learn to use the tools at you fingertips - IntelliSense, the Object Browser, and online help.
Scrolling through the options that IntelliSense gives you, or looking up InlineShape in the Object Browser, you would find LockAspectRatio. If you weren’t sure whether that was what you needed, pressing F1 would take you to the online help.

Find nearest Heading above the MS Word table

I am enumerating tables in Microsoft Word in a following way:
Dim doc As Document, t As Table
Set doc = ActiveDocument
For Each t In doc.Tables
Next t
Now I would like to find the nearest paragraph with "Heading 2" style above the table and get it's text into a variable. Great if it could be accomplished without changing the selection focus in the document.
I can enumerate paragraphs in the document, but how to determine that some paragraph is above some table?
I solved that by building a list of paragraph start positions:
Private Type CaptionRec
Text As String
EndPos As Long
End Type
Dim caps() As CaptionRec
Dim i As Long
Dim p As Paragraph
ReDim caps(0)
i = 0
For Each p In doc.Paragraphs
If p.Style = "Überschrift 2" Then
i = i + 1
ReDim Preserve caps(i)
caps(i).Text = TrimGarbageAtEnd(p.Range.Text)
caps(i).EndPos = p.Range.Start 'Ok, this should be the end, not the start
End If
Next p
... and finding the minimum distance between table start and a "Heading 2" paragraph from array:
Public Function GetClosestCaption(tableStart As Long, ByRef caps() As CaptionRec) As String
Dim cap As CaptionRec, distance As Long, minDistance As Long, res As String, i As Long
minDistance = 2147483647 'Max long
res = ""
For i = LBound(caps) To UBound(caps)
cap = caps(i)
distance = tableStart - cap.EndPos
If distance >= 0 Then
If distance < minDistance Then
minDistance = distance
res = cap.Text
End If
End If
Next i
GetClosestCaption = res
End Function
The routine gets called in a following loop:
Public Sub MainRoutine()
For Each t In doc.Tables
If table_validity_criteria_go_here Then
caption = GetClosestCaption(t.Range.Start, caps)
For Each r In t.Rows
'Enumerate rows
Next r
End If
Next t
End Sub
An alternative is to reverse the logic. Instead of processing the tables and then looking for the associated heading, find the headings then process the tables within the range of the heading level, For example:
Sub FindHeading2Ranges()
Dim findRange As Range
Dim headingRange As Range
Set findRange = ActiveDocument.Content
With findRange.Find
.ClearFormatting
.Forward = True
.Wrap = wdFindStop
.Format = True
.Style = ActiveDocument.Styles(wdStyleHeading2)
Do While .Execute
Set headingRange = findRange.GoTo(What:=wdGoToBookmark, Name:="\HeadingLevel")
If headingRange.Tables.Count > 0 Then
ProcessTables headingRange, TrimGarbageAtEnd(findRange.text)
End If
findRange.Collapse wdCollapseEnd
Loop
End With
End Sub
Sub ProcessTables(headingRange As Range, caption As String)
Dim t As Table
For Each t In headingRange.Tables
If table_validity_criteria_go_here Then
For Each r In t.Rows
'Enumerate rows
Next r
End If
Next t
End Sub

How can I convert all tables in a word document to images?

Below is my attempt but it's producing odd results, and the results seem to change each time i run the macro...
Can anyone see any issues in my code or can think of a better way of doing this?
Sub ConvertTablesToImages()
Dim tbl As Table
Dim currentDoc As Document
Set currentDoc = ActiveDocument
For Each tbl In currentDoc.Tables
tbl.Range.Copy
Selection.Collapse Direction:=wdCollapseStart
Selection.PasteSpecial DataType:=wdPasteEnhancedMetafile
tbl.Delete
Next
End Sub
You had a few problems. One is mixing ranges and selections and the other is deleting members of a collection (in this case tables in the document) while trying to loop through all of them.
Take a look at this revision of your code. It should work better.
Sub ConvertTablesToImages()
Dim tbl As Table, rng As Range, i As Integer
Dim currentDoc As Document
Set currentDoc = ActiveDocument
For i = currentDoc.Tables.Count To 1 Step -1
Set tbl = currentDoc.Tables(i)
Set rng = tbl.Range
rng.Collapse Direction:=wdCollapseStart
tbl.Range.CopyAsPicture
tbl.Delete
rng.PasteSpecial DataType:=wdPasteEnhancedMetafile
rng.ShapeRange(1).ConvertToInlineShape
Next
End Sub
Just adding a variation to Rich's answer for anyone who finds that their tables are being cropped in some of the pasted images.
Adding tbl.PreferredWidth = 0 seemed to fix this
Sub ConvertTablesToImages()
Dim tbl As Table, rng As Range, i As Integer
Dim currentDoc As Document
Set currentDoc = ActiveDocument
For i = currentDoc.Tables.Count To 1 Step -1
Set tbl = currentDoc.Tables(i)
Set rng = tbl.Range
tbl.PreferredWidth = 0
rng.Collapse Direction:=wdCollapseStart
tbl.Range.CopyAsPicture
tbl.Delete
rng.PasteSpecial DataType:=wdPasteEnhancedMetafile
rng.ShapeRange(1).ConvertToInlineShape
Next
End Sub

How to get all text between <strong> </strong> in MS word to turn Bold using VBA?

Basically I want to transform the text in between the tags into bold. This text will always be in the comments. The current code doesnt do anything.
I am not really sure if this code makes any sense at all, but I usually use VBA for Excel and word seems to be a bit trickier.
Sub Bold()
Dim eCom As Comment
Dim iFound As Integer
Dim rbold As Range
Dim iDot As Integer
Dim flag As Boolean
Dim aDoc As Document
Set aDoc = ActiveDocument
flag = True
Application.ScreenUpdating = False
For Each eCom In ActiveDocument.Comments
iFound = InStr(eCom.Range.Text, "<strong>")
iDot = 0
If iFound > 0 Then
iDot = InStrRev(eCom.Range, "</") - iFound + 1
Set rbold = aDoc.Range(Start:=eCom.Range.Start + iFound, End:=eCom.Range.Start + InStrRev(eCom.Range, "<"))
rbold.Select
Selection.Font.Bold = wdToggle
End If
Next eCom
Application.ScreenUpdating = True
End Sub
There are a few problems here. First, it appears that the Comment Ranges do not use the same numbering as the document ranges. So
Set rbold = aDoc.Range(Start:=eCom.Range.Start + iFound, End:=eCom.Range.Start + InStrRev(eCom.Range, "<"))
is not actually the range in the comments, it is instead a range in the document starting with the place in the comment that has the strong html tag.
Second, even if this was working, it would start the bolding in the wrong place, starting with "strong>"
Third, there's no reason to select the range, just set it to bold.
This code will do what you want (I commented out a line as I couldn't figure out what it was supposed to do):
Sub Bold()
Dim eCom As Comment
Dim iFound As Integer
Dim rbold As Range
Dim iDot As Integer
Dim flag As Boolean
Dim aDoc As Document
Dim newCom As Comment
Set aDoc = ActiveDocument
flag = True
Application.ScreenUpdating = False
For Each eCom In ActiveDocument.Comments
iFound = InStr(eCom.Range.Text, "<strong>")
iDot = 0
If iFound > 0 Then
'iDot = InStrRev(eCom.Range, "</") - iFound + 1
Set rbold = eCom.Range
rbold.MoveEnd Unit:=wdCharacter, Count:=-(Len(rbold) - InStrRev(rbold, "</") + 1)
rbold.MoveStart Unit:=wdCharacter, Count:=iFound + Len("<strong>") - 1
rbold.Bold = True
End If
Next eCom
Application.ScreenUpdating = True
End Sub