How to change filename using macro? - vba

Now I have a document and i want to save it but the filename should be iterative
eg. 1.docx then if i run the macro it should save as 2.docx and so on.
can I take a word from the document as the variable for the filename
eg. I want to use the 45th word in my document as the filename
So how can i do it?

I don't have word open at the moment, so cannot code specific examples. What follows is hints on how to find your answer.
In word, you can select a range of text. Within these ranges are a number of built-in collections. "Paragraphs" and "Words" are some of the examples.
The easiest way to address your question is to select the Document Range, and then iterate through the Words collection until you get to the 45th word.
However, MSDN notes that some non-words are also kept in this collection, so you may want to filter as you iterate. (as an aside, be careful with their example, deleting items in a for-each loop has consequences)
Assuming "rngWords" is your selected document range (words collection), you could would be similar to (not true code):
Counter = 0
selectedWord = ""
for iterator = 1 to rngWords.Count
loopWord = rngWords(iterator)
if trueword then Counter = Counter + 1
if counter = 45 then ' magic number = 45 based on your post.
selectedword = loopWord
exit loop
end if
next iterator
You can experiment (e.g. debug.print loopWord) to work out how to determine a true word. You will also have to address what happens if you don't have 45 words in your document - e.g. you could take the last word. Probably the best way is to check the count first (if rngWords.Count < 45 then .... else (loop))

To retrieve the no. from from document, use the below link
Getting char from string at specified index in the visual basic
and for saving the file
Sub ExampleToSaveWorkbook()
Workbooks.Add
'Saving the Workbook
ActiveWorkbook.SaveAs "C:\WorkbookName.xls"
'OR
'ActiveWorkbook.SaveAs Filename:="C:\WorkbookName1.xls"
End Sub

Related

Converting Headings to Bookmarks

I'm using Office 2016. I'd like to make a macro that loops through each heading in a document, and then creates a bookmark at the heading's location using the heading text (modified as necessary) as the bookmark name. Most of the headings are in X.X.X.X format, such as "3.3.4.1. sometexthere".
I'm still a beginner using VBA, but after a lot of googling I managed to adapt some Frankenstein code that almost works:
Sub HeadingsToBookmarks()
Dim heading As Range
Set heading = ActiveDocument.Range(Start:=0, End:=0)
Do
Dim current As Long
current = heading.Start
Set heading = heading.GoTo(What:=wdGoToHeading, Which:=wdGoToNext)
If heading.Start = current Then
Exit Do
End If
ActiveDocument.Bookmarks.Add MakeValidBMName(heading.Paragraphs(1).Range.Text), Range:=heading.Paragraphs(1).Range
Loop
End Sub
Function MakeValidBMName(strIn As String)
Dim pFirstChr As String
Dim i As Long
Dim tempStr As String
strIn = Trim(strIn)
pFirstChr = Left(strIn, 1)
If Not pFirstChr Like "[A-Za-z]" Then
strIn = "Section_" & strIn
End If
For i = 1 To Len(strIn)
Select Case Asc(Mid$(strIn, i, 1))
Case 49 To 58, 65 To 90, 97 To 122
tempStr = tempStr & Mid$(strIn, i, 1)
Case Else
tempStr = tempStr & "_"
End Select
Next i
tempStr = Replace(tempStr, " ", " ")
tempStr = Replace(tempStr, ":", "")
If Right(tempStr, 1) = "_" Then
tempStr = Left(tempStr, Len(tempStr) - 1)
End If
MakeValidBMName = tempStr
End Function
This code almost works, and makes appropriate bookmarks at some of the headings, but not all. Can anyone help me figure out what I need to fix here, or have other recommendations on how else I can clean up this code?
Edit: More information: The code above converts the first 5 or so headings in the document I've been testing it on, along with a few others scattered around. The second half of the code, which does the actual conversion, seems to work fine- the problem is located in the section that loops through each heading. The second half converts unusable characters to those that work with the requirements for bookmark names, and adds "Section_" to the beginning of bookmarks / headings that start with numbers (as bookmarks aren't allowed to start with numbers).
My goal is to be able to hyperlink to all sections within the document that has headings from a different word document. The standard Table of Contents creator allows only for links to be built within the same document, as far as I can tell. I'm aware that when word saves to PDF, it can convert headings to bookmarks; I would like to be able to do the same thing but retain the document in word format.
I unfortunately can't use the built in numbering. I'm working with documents that are already created and have a set and specific format.
You haven't described why you want bookmarks, or how a future user of the document would use/access the bookmarks.
MS Word has a number of built in features that act as bookmarks. The best way to do this is to use Styles. The built-in heading styles allow for some native navigation functionality (Word's own hidden bookmarks). Also, don't re-invent the wheel - use built in numbering.
This requires some document discipline. Use headings only for headings, and body text for the non-heading text.
The benefits make the discipline worth it. You can easily create tables of contents that use the headings (or even some of your custom styles), and headings show up in the navigation pane. When you save to PDF, you can use the headings as bookmarks in the PDF (show up on the Reader navigation bar).
Note that what I have described doesn't even touch VBA.
If you use set styles for your headings and you want to do a little more than what you can do natively, then you can simply:
Loop through all paragraphs in the document
See if that paragraph is set to your heading style
Place a bookmark (valid bookmark name!) over that paragraph
I have left the actual coding to you, but I think you will find it easy to do based on the pseudo code above. My pseudo code loop is not the only way to find the paragraphs, but it is the easiest to visualise.
Once you use the simplified method above and built-in numbering, you should find that you can modify your ValidBMName function - simplifying it. But, as noted and depending on why you want bookmarks, you may be able to avoid VBA altogether.
This code works for me:
Sub HeadingsToBookmarks()
Dim heading As Range
Set heading = ActiveDocument.Range(Start:=0, End:=0)
Do
Dim current As Long
current = heading.Start
Set heading = heading.GoTo(What:=wdGoToHeading, Which:=wdGoToNext)
If heading.Start = current Then
Exit Do
End If
'This is the part I changed: ListFormat.ListString
ActiveDocument.Bookmarks.Add MakeValidBMName(heading.Paragraphs(1).Range.ListFormat.ListString), Range:=heading.Paragraphs(1).Range
Loop
End Sub

Programmatically Update Linked Named Range of excel object in MS Word (2007)

First question, excuse me if this has already been solved, but I've searched thoroughly and cannot find an answer:
I have linked several named ranges into a word document. This word doc (and the related excel workbook with named ranges) is a template: it's for a coworker who will make many copies of these templates (of both the word doc and the excel workbook).
I would like to include a command button in the word doc that, when clicked, will update the sources for the linked named ranges. Specifically, I want it to set the workbook with the same name as the worddoc, as the source.
The issue is that it does not like the named range I have entered. I get the:
Run-time error '6083': Objects in this document contain links to files that cannot be found. The linked information will not be updated.`
However, I have quadrupled-checked my excel doc, the named range exists. AND, when I hit Alt+F9 in word, I clearly see the link contains the named range!
{LINK Excel.Sheet.8 C:\Users\Marc\Documents\WIP_SSS.xlsm CED \a \p}
Here is my code:
Public Sub ChangeSource()
Dim filename As Variant
Dim fieldcount As Integer
Dim x As Integer
filename = Left(Application.ActiveDocument.Name, Len(Application.ActiveDocument.Name) - 4) & "xlsm"
fieldcount = ActiveDocument.Fields.Count
For x = 1 To fieldcount
'Debug.Print ActiveDocument.Fields(x).Type
If ActiveDocument.Fields(x).Type = 56 Then
ActiveDocument.Fields(x).LinkFormat.SourceFullName = ActiveDocument.Path & "\" & _
filename & "!CED"
End If
Next x
End Sub
If I don't enter the named range at all, the macro works, but it embeds the entire excel worksheet (which I do not want it to do). Any ideas on how/ why it is not liking the named range?
Thanks,
Marc
UPDATE:
With help from Bibadia, I found a solution; in addition, I want to document some strange behavior exhibited by Word VBA:
First off, the solution code:
Public Sub ChangeSource()
Dim filename As Variant
Dim fieldcount As Integer
Dim x As Integer
filename = ThisDocument.Path & "\" & Left(Application.ActiveDocument.Name, Len(Application.ActiveDocument.Name) - 4) & "xlsm"
fieldcount = ActiveDocument.Fields.Count
For x = 1 To fieldcount
On Error Resume Next
If ActiveDocument.Fields(x).Type = 56 Then
ActiveDocument.Fields(x).Delete
End If
Next x
ActiveDocument.Bookmarks("R1").Range.InlineShapes.AddOLEObject filename:=filename & "!Range1", LinkToFile:=True
End Sub
I first deleted all type 56 fields (linked object, or more technically, "wdfieldlinked"). Then, I added OLEObjects at pre-set bookmark locations.
Interestingly, just as Bibadia noted, the key was to input the LinkToFile:=True code. It seems Word will not accept the object if it is embedded: if I remove that line, I get the error Word Cannot obtain the data for the C:\...\document!NamedRange link.
Finally, I found one other odd behavior: When trying to simply replace the link, using this code,
ActiveDocument.Fields(1).LinkFormat.SourceFullName = filepath+name & _
"!CED" 'that is the named range
it would work once, when I changed both the word document's and the excel workbook's filenames (see original message for context). So, when the new filepath+name DID NOT match the existing filepath+name, Word VBA accepted the change. However, once initially updated, if I tried to run the macro again, I would get:
run-time error '6083': Objects in this document contain links to files that cannot be found. The linked information will not be updated.
I would get this error even if I changed the named range to another named range in the same worksheet (and obviously same workbook). So it appears that Word VBA does not like "updating" filepath+name when the filepath+name does not change.
Just so anyone who didn't know (like me) now knows. Sorry for the long update, I just wanted to be thorough.
I am not completely sure of this, but it is a little too long for a comment.
As far as I know, you can only set LinkFormat.FullSourceName to the name of a file, not a fullname + subset name, which is what you are trying to do when appending the "!CED". Although you can read the subset name (CED) from OleFormat.Label, you can't modify it as it's a read-only property.
So if you actually need to modify the subset name (CED), AFAICS the only way to do it is to delete and reinsert the LINK field. If you reinsert using Fields.Add, you just specify the text of the field, so you can get the file name and Subset name right. What is slightly confusing is that if you insert a LINK using InlineShapes.AddOleObject, you can specify fullname+subset name in exactly the way that you are trying to do in your code.
However, I do not think you are trying to modify the Subset name. So let's assume that you already have a LINK field along the lines of
{ LINK Excel.SheetMacroEnabled.12 "the full pathname of a .xlsm file" CED \a f 0 \p }
Word will only be able to update that link if the path+filename is valid (i.e. there's a .xlsm at that location, the workbook has a Range Name called CED, and the Range Name is in the first Sheet. Otherwise, you have to specify a Sheet name as well, e.g.
{ LINK Excel.SheetMacroEnabled.12 "the full pathname of a .xlsm file" Sheet2!CED \a f 0 \p }
It's just a guess, but if your code is trying to connect to a Workbook where the range defined by CED is not in the first sheet, you would see the error you describe.
Further, the scope of the CED Range Name has to be either "workbook" or the name of the first sheet. Otherwise, if the scope is the first sheet but the range is actually in another sheet, or vice versa, I do not think Word can make the connection whatever subset name you provide (my guess is that Word never really caught up with Excel after Excel introduced multi-sheet workbooks).
If CED can reference sheets other than the first one, I think you will probably have to use the Excel object model to discover which sheet its Range is in, construct the appropriate Subset name, and delete/re-insert the LINK field.

Error 1004 with VBA code with bookmarks

I am using a macro to populate a word document with text from named ranges in excel. The word document has bookmarks that correspond with the named excel ranges. I did not write the code, but rather copied it from another source.
There is quite a bit more to this macro than the snippet I posted. I could post the rest if that is useful. I had about half of my word document bookmarked and the macro was working fine then it suddenly stopped working.
I am receiving a error 1004 in the line highlighted below. I am a newbie so I'm not even quite sure what I should be searching for to fix this issue. Any assistance you could provide would be appreciated! Thanks in advance!
P.S. In case it's relevant, I am using Word and Excel 2007
'PASTE TEXT STRINGS LOOP
n = 1
For Each temp In BkmTxt
p = p + 1
Prompt = "Please wait. Copying text. Carrying out operation " & p & " of " & pcount & "."
Application.StatusBar = Prompt
'If The Bkmtxt(n) is empty then go to the next one, once that has been found do next operation.
If BkmTxt(n) = Empty Then
n = n + 1
'should find match and work
Else
'You might want to use multiple copies of the same text string.
'In this case you need to call the bookmark as follows: "ARTextWhatever_01"
'You can use as many bookmarks as you want.
BkmTxtSplit = Split(BkmTxt(n), "_")
vValue = Range(BkmTxtSplit(0)).Text **<----- ERROR HERE**
Set wdRng = wdApp.ActiveDocument.Bookmarks(BkmTxt(n)).Range
If Len(sFormat) = 0 Then
'replace the bookmark text
wdRng.Text = vValue
Else
'replace the bookmark text with formatted text
wdRng.Text = Format(vValue, sFormat)
End If
'Re-add the Bookmark
wdRng.Bookmarks.Add BkmTxt(n), wdRng
n = n + 1
End If
Next
Step 1: Don't copy code from external sources. Use external sources as a learning tool and try to understand what they are actually doing.
Now if I understand you correctly, you simply have an Excel sheet with named ranges, I assume they have information already within them, and a word document with bookmarks that EXACTLY match the named ranges:
Step 2: Make sure you have the word object library reference within excel
Here:
sub KeepItDoin()
dim xlRange as Excel.Range
dim wdApp as new Word.Application
dim wdDoc as Word.Document
dim wdBkm as Word.Bookmark
set wdDoc = wdApp.Documents.Open( "Filepath" ) 'get filepath from where ever
for each wdBkm in wdDoc.Bookmarks
set xlRange = Application.Range(wdBkm.Name)
wdBkm.range.text = xlRange.Value
next wdBkm
end sub
That will get you close probably (didn't test, don't care if it works. Use it to learn). The idea is that if the bookmarks match up to the range, we can use their names to find the ranges in excel and then tell excel to move the data within it into the bookmarks range.
You will likely need to add some formatting or maybe create a table and then move cell by cell in the range and fill the table but this is as close as I'm willing to get since you like to copy pasta.
In case anyone is interested, I figured it out. There was an error with the bookmarks I inserted into my Word document. This macro returns Error 1004 if the word document contains a bookmark that does not correspond to a range in excel. Thank you for your help.

Distinguishing Table of Contents in Word document

Does anyone know how when programmatically iterating through a word document, you can tell if a paragraph forms part of a table of contents (or indeed, anything else that forms part of a field).
My reason for asking is that I have a VB program that is supposed to extract the first couple of paragraphs of substantive text from a document - it's doing so by iterating through the Word.Paragraphs collection. I don't want the results to include tables of contents or other fields, I only want stuff that a human being would recognize as a header, title or a normal text paragraph. However it turns out that if there's a table of contents, then not only the table of contents itself but EVERY line in the table of contents appears as a separate item in Word.Paragraphs. I don't want these but haven't been able to find any property on the Paragraph object that would allow me to distinguish and so ignore them (I'm guessing I need the solution to apply to other field types too, like table of figures and table of authorities, which I haven't yet actually encountered but I guess potentially would cause the same problem)
Because of the limitations in the Word object model I think the best way to achieve this would be to temporarily remove the TOC field code, iterate through the Word document, and then re-insert the TOC. In VBA, it would look like this:
Dim doc As Document
Dim fld As Field
Dim rng As Range
Set doc = ActiveDocument
For Each fld In doc.Fields
If fld.Type = wdFieldTOC Then
fld.Select
Selection.Collapse
Set rng = Selection.Range 'capture place to re-insert TOC later
fld.Cut
End If
Next
Iterate through the code to extract paragraphs and then
Selection.Range = rng
Selection.Paste
If you are coding in .NET this should translate pretty closely. Also, this should work for Word 2003 and earlier as is, but for Word 2007/2010 the TOC, depending on how it is created, sometimes has a Content Control-like region surrounding it that may require you to write additional detect and remove code.
This is not guaranteed, but if the standard Word styles are being used for the TOC (highly likely), and if no one has added their own style prefixed with "TOC", then it is OK. This is a crude approach, but workable.
Dim parCurrentParagraph As Paragraph
If Left(parCurrentParagraph.Format.Style.NameLocal, 3) = "TOC" Then
' Do something
End If
What you could do is create a custom style for each section of your document.
Custom styles in Word 2003 (not sure which version of Word you're using)
Then, when iterating through your paragraph collection you can check the .Style property and safely ignore it if it equals your TOCStyle.
I believe the same technique would work fine for Tables as well.
The following Function will return a Range object that begins after any Table of Contents or Table of Figures. You can then use the Paragraphs property of the returned Range:
Private Function GetMainTextRange() As Range
Dim toc As TableOfContents
Dim tof As TableOfFigures
Dim mainTextStart As Long
mainTextStart = 1
For Each toc In ActiveDocument.TablesOfContents
If toc.Range.End > mainTextStart Then
mainTextStart = toc.Range.End + 1
End If
Next
For Each tof In ActiveDocument.TablesOfFigures
If tof.Range.End > mainTextStart Then
mainTextStart = tof.Range.End + 1
End If
Next
Set GetMainTextRange = ActiveDocument.Range(mainTextStart, ActiveDocument.Range.End)
End Function

copy Word document contents without using clipboard (VBA)

I was wondering how to avoid using Windows clipboard, when you want to "replicate" multiple sections of a Word document (using VBA in macros)
Why to avoid? Because we're using Word on a server, in a multiuser environment (I know that it is officially frowned upon)
Otherwise, this would be easily accomplished with Selection.Copy and Selection.Paste methods.
Thanks.
I finally resolved to copy word by word. FormattedText seemed to work fairly well, until the last word (some special (evidently) characters), where suddenly the cell that I just filled with copied content would go blank. When I increased the number of cells, other run-time errors would pop up, like Your table got corrupted, and other ambiguous ones. Somehow, the source cell that I was copying from always seemed to have these peculiar chars in the end with ASCII codes 13 and 7. I know what 13 means, but 7?
Anyway, I decided to copy everything apart from this last character with code 7. It seems to work alright. Both formatting and fields are copied too.
In any case, the whole story proved to me for one more time that programming in VBA is mostly trial-and-error occupation. You are never sure when something might break.. unless I am missing update on some crucial concepts..
Here's the chunks of the code I used. The idea is that first we have a document with a single 1x1 cell table, with some rich text content. In the first piece of the code (inside a macro) I multiply the cells:
Dim cur_width As Integer, i As Integer, max_cells As Integer, cur_row As Integer
Dim origin_width As Integer
If ActiveDocument.Tables.Count = 1 _
And ActiveDocument.Tables(1).Rows.Count = 1 _
And ActiveDocument.Tables(1).Columns.Count = 1 _
Then
max_cells = 7 ' how many times we are going to "clone" the original content
i = 2 ' current cell count - starting from 2 since the cell with the original content is cell number 1
cur_width = -1 ' current width
cur_row = 1 ' current row count
origin_width = ActiveDocument.Tables(1).Rows(1).Cells(1).Width
' loop for each row
While i <= max_cells
' adjust current width
If cur_row = 1 Then
cur_width = origin_width
Else
cur_width = 0
End If
' loop for each cell - as long as we have space, add cells horizontally
While i <= max_cells And cur_width + origin_width < ActiveDocument.PageSetup.PageWidth
Dim col As Integer
' \ returns floor() of the result
col = i \ ActiveDocument.Tables(1).Rows.Count
// 'add cell, if it is not already created (which happens when we add rows)
If ActiveDocument.Tables(1).Rows(cur_row).Cells.Count < col Then
ActiveDocument.Tables(1).Rows(cur_row).Cells.Add
End If
// 'adjust new cell width (probably unnecessary
With ActiveDocument.Tables(1).Rows(cur_row).Cells(col)
.Width = origin_width
End With
// 'keep track of the current width
cur_width = cur_width + origin_width
i = i + 1
Wend
' when we don't have any horizontal space left, add row
If i <= max_cells Then
ActiveDocument.Tables(1).Rows.Add
cur_row = cur_row + 1
End If
Wend
End If
In the second part of the macro I populate each empty cell with the contents of the first cell:
' duplicate the contents of the first cell to other cells
Dim r As Row
Dim c As Cell
Dim b As Boolean
Dim w As Range
Dim rn As Range
b = False
i = 1
For Each r In ActiveDocument.Tables(1).Rows
For Each c In r.Cells
If i <= max_cells Then
// ' don't copy first cell to itself
If b = True Then
' copy everything word by word
For Each w In ActiveDocument.Tables(1).Rows(1).Cells(1).Range.Words
' get the last bit of formatted text in the destination cell, as range
' do it first by getting the whole range of the cell, then collapsing it
' so that it is now the very end of the cell, and moving it one character
' before (because collapsing moves the range actually beyond the last character of the range)
Set rn = c.Range
rn.Collapse Direction:=wdCollapseEnd
rn.MoveEnd Unit:=wdCharacter, Count:=-1
' somehow the last word of the contents of the cell is always Chr(13) & Chr(7)
' and especially Chr(7) causes some very strange and murky problems
' I end up avoiding them by not copying the last character, and by setting as a rule
' that the contents of the first cell should always contain an empty line in the end
If c.Range.Words.Count <> ActiveDocument.Tables(1).Rows(1).Cells(1).Range.Words.Count Then
rn.FormattedText = w
Else
'MsgBox "The strange text is: " & w.Text
'the two byte values of this text (which obviously contains special characters with special
'meaning to Word can be found (and watched) with
'AscB(Mid(w.Text, 1, 1)) and AscB(Mid(w.Text, 2, 1))
w.MoveEnd Unit:=WdUnits.wdCharacter, Count:=-1
rn.FormattedText = w
End If
Next w
End If
b = True
End If
i = i + 1
Next c
Next r
Here are the images of the Word document in question. First image is before running the macro, second is between the first chunk of code and the last, while the third image is the resulting document.
Image 1
Image 2
Image 3
That's it.
In Office 2007+ VSTO, you can export the block with Range.ExportFragment and then go to your new document and import it with Range.ImportFragment. I haven't used this in production, but experimented with it and it seems to work OK.
One caveat, I got errors when trying to export as a .docx, but RTF seemed to work ok.
Both methods exist in VBA as well, but I only tested the VSTO methods.
This doesn't always work, with text fields, diagramms, for example, or if you need to copy it to another document, but it's good for copying simple formatted text inside one document.
'First select something, then do
Word.WordBasic.CopyText
'Then move somewhere
Word.WordBasic.OK;
To copy the whole document to a new document use this:
Word.Application.Documents.Add Word.ActiveDocument.FullName
I ran into a similar issue. I wanted to copy a table from one word doc to another using Powershell without using the clipboard. Since a user using the computer while the script ran could break the script by using the clipboard. The solution I came up with was:
Open the source document and put a bookmark covering the range of what I wanted (in my case a single table).
Save the source document with its bookmark in another location (to avoid changing the source document).
Opened the destination document and created a range object for where I wanted the table placed.
Used range.InsertFile with the first parameter of the source file with my bookmark and the second parameter of my bookmark name. This single operation pulled the entire table plus source formatting directly into the destination document.
I then added code based on where the insertion was being done and how much longer the story was to select the inserted table to allow further operations on it.
I tried many any other methods to move the table and this was by far the best. Sorry I can't provide VBA code for this solution.
Use the Text property of the Selection object to put the data into a string variable rather than onto the clipboard:
Dim strTemp as String
strTemp = Selection.Text
You can then insert the text stored in the variable elsewhere as needed.