How can one find a only content (i.e. text) differences between two powerpoint files?
I use PPT 2013. It has a compare tool, but that also finds all text box movements, animation changes, formatting differences etc., which makes it difficult to see whether there are any text changes.
I need to compare "text only" and display any change in text
Context
My client gives me a PPT file. I will format it (color, font, animation etc.), but I'm not supposed to change any text content. If I delete or insert any content by mistake, I'd like to be able to detect that, so I can revert it.
In PowerPoint 2013 you can export the PowerPoint file content to word or a pdf file which you can then use to compare only the text changes:
choose File> Export, Create Handouts, then click
the Create Handouts button.
In the dialog box that opens, choose the Outline Only option and
click OK. Word opens with your text.
Update:
You can run this VBS script to extract the text to a text file then you can compare the two files.
It comes from the tool Beyond Compare 4 which can be downloaded as a trial edition. Download the Additional File Formats for PowerPoint files as well if you just want to compare the text changes from the tool.
' PPT_to_TXT.vbs
'
' Extracts plain text from a PowerPoint document. Requires Microsoft PowerPoint.
' Usage:
' WScript PPT_to_TXT.vbs <input file> <output file>
Option Explicit
' MsoAutomationSecurity
Const msoAutomationSecurityForceDisable = 3
' OpenTextFile iomode
Const ForAppending = 8
Dim App, AutoSec, Doc, FileSys
Set FileSys = CreateObject("Scripting.FileSystemObject")
If FileSys.FileExists(WScript.Arguments(1)) Then
FileSys.DeleteFile WScript.Arguments(1)
End If
Set App = CreateObject("Powerpoint.Application")
On Error Resume Next
App.DisplayAlerts = False
AutoSec = App.AutomationSecurity
App.AutomationSecurity = msoAutomationSecurityForceDisable
Err.Clear
Dim Comment, Shape, Slide, TgtFile
Set Doc = App.Presentations.Open(WScript.Arguments(0), True, , False)
If Err = 0 Then
Set TgtFile = FileSys.OpenTextFile(WScript.Arguments(1), ForAppending, True)
For Each Slide In Doc.Slides
For Each Shape In Slide.Shapes
If Shape.HasTextFrame Then
If Shape.TextFrame.HasText Then
TgtFile.WriteLine Shape.TextFrame.TextRange.Text
End If
End If
Next
For Each Shape In Slide.NotesPage.Shapes
If Shape.HasTextFrame Then
If Shape.TextFrame.HasText Then
TgtFile.WriteLine Shape.TextFrame.TextRange.Text
End If
End If
Next
For Each Comment In Slide.Comments
TgtFile.WriteLine Comment.Author & vbTAB & Comment.DateTime & vbTAB & Comment.Text
Next
Next
TgtFile.Close
Doc.Close
End If
App.AutomationSecurity = AutoSec
App.Quit
Related
Assuming I have:
a word template including macro: custom_template.dotm;
two bookmarks in this template: 'first_name" and "last_name".
I would like that on "Save" event, in the dialog box, the application proposes to user, instead of "document1", and only if relative bookmarks exist, the filename "Document of first_name second_name.docx".
Can anybody explain me how to achieve this with VBA?
Thanks.
=== UPDATE ===
Now I've this code, working well when I execute it.
I would like it runs automatically when user clicks on "save document".
Sub Demo()
Dim sFlNm As String
With ActiveDocument
sFlNm = "Document of " & .Bookmarks("first_name").Range.Text & " " & .Bookmarks("last_name").Range.Text
End With
With Dialogs(wdDialogFileSaveAs)
.Name = sFlNm
.Show
End With
End Sub
For a macro to run at the client's end, you would have to send a macro-enabled template or document. Many people who are running anti-virus software will get a warning of a possible Word virus. Then the user will have to manually enable the macros. Are they going to bother?
Making it run automatically with a Save command may have unintended consequences. You'll have to check whether the bookmarks have actually been filled, and using the Save command while you're revising the document can save it with a new file name. But you asked, so here's how: rename the macro as FileSave. Then when you choose Ctrl + S or File>Save in Word, the dialog will automatically pop up:
Sub FileSave()
Dim sFlNm As String
With ActiveDocument
sFlNm = "Document of " & .Bookmarks("first_name").Range.Text & " " & .Bookmarks("last_name").Range.Text
End With
With Dialogs(wdDialogFileSaveAs)
.Name = sFlNm
.Show
End With
End Sub
In Word, to create a macro that runs automatically when you choose a Word command, follow these steps:
Choose Developer>Macros.
Change the Macros in dropdown to Word commands.
Choose the command name you want to re-purpose.
Change the Macros in dropdown back to the macro-enabled document or template that you're developing.
Click on the Create button. A new macro is created in the VBE with the correct command name. Fill in the macro with whatever you want the macro to do.
Is there a way to save a single slide as a PNG image with VBA that does not use Slides(#).Export? I am trying to save the image directly to SharePoint but the export causes an error if I make the save location anywhere besides my machine. I know that saving directly to SharePoint as an image is possible since I can click through the save as menu, but I cant replicate with VBA.
Application.FileDialog(msoFileDialogSaveAs _
).InitialFileName = "https://home.thesite.com/sample"
intChoice = Application.FileDialog(msoFileDialogSaveAs).Show
If intChoice <> 0 Then
sImagePath = Application.FileDialog(msoFileDialogSaveAs _
).SelectedItems(1)
Else
MsgBox "Action Cancelled"
Exit Sub
End If
CurSlide = ActiveWindow.Selection.SlideRange.SlideIndex
ActivePresentation.Slides(CurSlide).Export sImagePath, "PNG"
Turns out Export will work if the link is modified so instead of
"https://home.thesite.com/sample"
its
"\\home.thesite.com#SSL\DavWWWRoot\sample"
I am very new with Powerpoint VBA and would like to know if there is a short way to transfer one text range from PowerPoint A to another text range located in Powerpoint B in a specific sequence.
Page a1 = b1
Page a2 = b2
Page a3 = b3
The template is changing and I need to adapt 5 powerpoints of 100 slides so I tought it would be easier with this solution.
Thank you in advance for your help.
PRECISION : I don't want to copy and paste the text range but to copy the text inside the range to put it inside the new range. Please find below the code I already have but It doesnt' Paste it inside my new range.
Sub copier_texte() 'je veux copier le contenu de la forme, et non pas la forme en entier
Dim nb_slide As Integer
nb_slide = ActivePresentation.Slides.Count
With ActivePresentation
.Slides(1).Shapes(2).TextFrame.TextRange.Copy 'je sélectionne uniquement le contenu de la forme
For i = 2 To .Slides.Count
.Slides(i).Select
ActiveWindow.View.Paste
Next i
End With
End Sub
Short Answer:
Is there're a short way to transfer one text range from PowerPoint A to another text range located in Powerpoint B?
I think that there's no short way to do it, but let's try something first!
Long Answer:
Note: This solution based not on your desired behaviour (since it's unclear for me and there're many and more "what if" cases), but on similar problem, so I think that it's legit. Anyway it's a good fundament to start of.
Input:
I dont know how exactly your presentations looks like, so I made a reference one (Presentation A) and a "broken" one (Presentation B). Let's take a look on them:
Presentation A (5 slides: 1x"Title slide" with 2 triangle shapes, 3x"Title and Content" slides, 1x"Section Header" slide):
Presentation B (5 slides: 1x"Title slide" missing triangle shapes, 3x"Title and Content" slides with empty/without shapes(placeholders), 1x"Blank" slide (wrong layout)):
Both presentations are in the same folder:
Desired behaviour:
Some sort of synchronisation, if we miss a shape - then create one and put desired text to it, if there's one - put desired text only (based on Presentations A's shape). There're some "what if" cases in logic:
"What if" the number of slides in each presentation isn't equal? In which order compare slides then? (In our case the number is equal, so in code we drop that part and compare slides pair by pair).
"What if" the compared slides have a different layout? (In our case difference in blank layout, so we can easily handle it, but what we should do in general?)
...and many other cases not considered in this solution
Logic:
Logic is plain and simple. The entry point to our routine is in the Presentation A, since it's an our reference file. From that point we acquire a reference to Presentation B (when opening it), and start iteration in two loops (thru each pair of slides and thru reference shapes).
If we found a "broken" (or not so, there's no check for that) shape by a reference one - we put text and some options in it or create a new one shape (or placeholder) otherwise.
Option Explicit
Sub Synch()
'define presentations
Dim ReferencePresentation As Presentation
Dim TargetPresentation As Presentation
'define reference objects
Dim ReferenceSlide As Slide
Dim ReferenceSlides As Slides
Dim ReferenceShape As Shape
'define target objects
Dim TargetSlide As Slide
Dim TargetSlides As Slides
Dim TargetShape As Shape
'define other variables
Dim i As Long
'Setting-up presentations and slide collections
Set ReferencePresentation = ActivePresentation
With ReferencePresentation
Set TargetPresentation = Presentations.Open(FileName:=.Path & "/Presentation B.pptm", _
WithWindow:=msoFalse)
Set ReferenceSlides = .Slides
End With
Set TargetSlides = TargetPresentation.Slides
'Check slide count
If ReferenceSlides.Count <> TargetSlides.Count Then
'What's a desired behaviour for this case?
'We can add slides to target presentation but it adds complexity
Debug.Print "ERROR!" & vbTab & "Reference And Target slides counts are not equal!"
Else
'"mainloop" for slides
For i = 1 To ReferenceSlides.Count
Set ReferenceSlide = ReferenceSlides(i)
Set TargetSlide = TargetSlides(i)
'Check slide layout
If ReferenceSlide.Layout <> TargetSlide.Layout Then
'What's a desired behaviourfor this case?
'We can change layout for target presentation but it adds complexity
'But let's try to change a layout too, since we have an easy case in our example!
Debug.Print "WARNING!" & vbTab & "Reference And Target slides layouts are not same!"
TargetSlide.Layout = ReferenceSlide.Layout
End If
'"innerloop" for shapes (for placeholders actually)
With ReferenceSlide
For Each ReferenceShape In .Shapes
Set TargetShape = AcquireShape(ReferenceShape, TargetSlide, True)
If TargetShape Is Nothing Then
Debug.Print "WARNING!" & vbTab & "There's no shape like " & ReferenceShape.Name
ElseIf TargetShape.HasTextFrame Then
With TargetShape.TextFrame.TextRange
'paste text
.Text = ReferenceShape.TextFrame.TextRange.Text
'and options
.Font.Size = ReferenceShape.TextFrame.TextRange.Font.Size
.Font.Name = ReferenceShape.TextFrame.TextRange.Font.Name
.Font.Color.RGB = ReferenceShape.TextFrame.TextRange.Font.Color.RGB
'...
End With
End If
Next
End With
Next
End If
'Save and close target presentation
Call TargetPresentation.Save
Call TargetPresentation.Close
End Sub
Function AcquireShape(ByRef ReferenceShape As Shape, ByRef TargetSlide As Slide, _
Optional ByVal CreateIfNotExists As Boolean) As Shape
Dim TargetShape As Shape
With ReferenceShape
'seek for existed shape
For Each TargetShape In TargetSlide.Shapes
If TargetShape.Width = .Width And TargetShape.Height = .Height And _
TargetShape.Top = .Top And TargetShape.Left = .Left And _
TargetShape.AutoShapeType = .AutoShapeType Then
Set AcquireShape = TargetShape
Exit Function
End If
Next
'create new
If CreateIfNotExists Then
If .Type = msoPlaceholder Then
Set AcquireShape = TargetSlide.Shapes.AddPlaceholder(.PlaceholderFormat.Type, .Left, .Top, .Width, .Height)
Else
Set AcquireShape = TargetSlide.Shapes.AddShape(.AutoShapeType, .Left, .Top, .Width, .Height)
End If
End If
End With
End Function
Output:
I know that it's hard to find any difference by a screenshot (it's can be even photoshoped, anyway there're a few difference for that purpose), but for a full answer, here it is:
Conclusion:
As you see, it isn't a hard task to achieve something similar to your desire, but complexity of solution depends on inputs and on "what if" cases, hence there's no short way to overcome this task in general (in my humble opinion). Cheers!
Your question has a number of different interpretations, below is my attempt to answer what I believe the question is. There are a number of stage to this solution.
1. Ensure we save the VBA we write
Firstly, we have to assume a master presentation, that is one that will hold the values to be copied into all others. This will need to be saved as a macro enabled presentation (pptm) to allow us to save our VBA. This is done via File > Save-As and while selecting the save location choose PowerPoint Macro-Enabled Presentation in the Save as type box.
2. Enable Windows scripting runtime
Within the pptm 'master' presentation that we now have, open the VBA IDE (Alt+F11). In the menu bar select Tools > References... and tick Microsoft Scripting Runtime from the list that is presented. Click OK to close the references dialog box with your tick remembered. This is needed for some error handling in the code, it checks to see if the presentation exists before trying to open it.
3. Insert the provided code
Right-click on VBAProject in the upper right area (the Project explorer) and select Insert > Module.
In the main editing area paste the below (I have added commenting to describe what is happening): -
Option Explicit
Public Sub Update()
Dim AryPresentations(4) As String
Dim LngPID As Long
Dim FSO As New FileSystemObject
Dim PP_Src As Presentation
Dim PP_Dest As Presentation
Dim Sld_Src As Slide
Dim Sld_Dest As Slide
Dim Shp_Src As Shape
Dim Shp_Dest As Shape
Dim LngFilesMissing As Long
Dim BlnWasOpen As Boolean
'If there is an error, this will handle it and stop the process
On Error GoTo ErrorHandle
'Increase the size of AryPresentations and and the paths as shown in the example below
AryPresentations(0) = "C:\Users\garye\Desktop\PP2.pptx"
AryPresentations(1) = "C:\Users\garye\Desktop\PP3.pptx"
AryPresentations(2) = "C:\Users\garye\Desktop\PP4.pptx"
AryPresentations(3) = "C:\Users\garye\Desktop\PP5.pptx"
AryPresentations(4) = "C:\Users\garye\Desktop\PP6.pptx"
'PP_Src is this, our 'master' presentation
Set PP_Src = ActivePresentation
'This loops through each item in AryPresentations
For LngPID = 0 To UBound(AryPresentations, 1)
'We rememeber if you had it open already as if you did, then we won't close it when we are done
BlnWasOpen = False
'Check all currently open presentations to see if one if the presentation we are due to update
For Each PP_Dest In PowerPoint.Presentations
If Trim(UCase(PP_Dest.FullName)) = Trim(UCase(AryPresentations(LngPID))) Then Exit For
Next
'If it was not already open, check it exists and if it does, then open in
If PP_Dest Is Nothing Then
If FSO.FileExists(AryPresentations(LngPID)) Then
Set PP_Dest = PowerPoint.Presentations.Open(AryPresentations(LngPID))
End If
Else
BlnWasOpen = True
End If
If PP_Dest Is Nothing Then
Debug.Print "File note found"
LngFilesMissing = LngFilesMissing + 1
Else
'The below connects to the slide (Sld_Src) you want to pick up from, the shape (Shp_Src) you want to pick up from and then
'places it in the slide (Sld_Dest) you want it to go to into the shape (Shp_Dest) you want it to go in to
Set Sld_Src = PP_Src.Slides(1)
Set Sld_Dest = PP_Dest.Slides(1)
Set Shp_Src = Sld_Src.Shapes(1)
Set Shp_Dest = Sld_Dest.Shapes(1)
Shp_Dest.TextFrame.TextRange.Text = Shp_Src.TextFrame.TextRange.Text
Set Shp_Dest = Nothing
Set Shp_Src = Nothing
Set Sld_Dest = Nothing
Set Sld_Src = Nothing
'Repeat the above for each piece of text to copy
'Finally save the changes
PP_Dest.Save
'Close the presentation if it was not already open
If Not BlnWasOpen Then PP_Dest.Close
End If
Next
MsgBox "Process complete. Number of missing files: " & LngFilesMissing, vbOKOnly + vbInformation, "Complete"
Exit Sub
ErrorHandle:
MsgBox "There was an error: - " & vbNewLine & vbNewLine & Err.Number & ": " & Err.Description, vbOKOnly + vbExclamation, "Error"
Err.Clear
End Sub
4. Customise code
You'll want to add the paths and location of the changes in and then it should run.
The problem I have got is that my corporate template set uses a SaveDate field in the footer of every word document - which is used to detail when the document was saved, which ties in with our custom document management system.
Subsequently, when users want to make a PDF of an old document, using the Save As PDF function of Office 2010, the Save Date is updated - creating a PDF of the old document, but with today's date. This is wrong. We are just trying to create a true PDF version of whatever the original document has in it.
To get around this, I am writing a macro solution which locks the fields, exports the document as a PDF and then unlocks the fields again.
I have come up against an issue where I can identify and lock all fields in the headers/footers (which is actually what I'm trying to do) but to make it more robust, need to find out a way to lock ALL FIELDS in ALL SECTIONS.
Showing you my code below, how can I identify all fields in all sections? Will this have to be done using the Index facility?
Sub CPE_CustomPDFExport()
'20-02-2013
'The function of this script is to export a PDF of the active document WITHOUT updating the fields.
'This is to create a PDF of the document as it appears - to get around Microsoft Word 2010's native behaviour.
'Route errors to the correct label
'On Error GoTo errHandler
'This sub does the following:
' -1- Locks all fields in the specified ranges of the document.
' -2- Exports the document as a PDF with various arguments.
' -3- Unlocks all fields in the specified ranges again.
' -4- Opens up the PDF file to show the user that the PDF has been generated.
'Lock document fields
Call CPE_LockFields
'Export as PDF and open afterwards
Call CPE_ExportAsPDF
'Unlock document fields
Call CPE_UnlockFields
'errHandler:
' MsgBox "Error" & Str(Err) & ": " &
End Sub
Sub CPE_LockFields()
'Update MS Word status bar
Application.StatusBar = "Saving document as PDF. Please wait..."
'Update MS Word status bar
Application.StatusBar = "Locking fields in all section of the active document..."
'Declare a variable we can use to iterate through sections of the active document
Dim docSec As section
'Loop through all document sections and lock fields in the specified ranges
For Each docSec In ActiveDocument.Sections
docSec.Footers(wdHeaderFooterFirstPage).Range.fields.Locked = True
docSec.Footers(wdHeaderFooterPrimary).Range.fields.Locked = True
docSec.Footers(wdHeaderFooterEvenPages).Range.fields.Locked = True
Next
End Sub
Sub CPE_UnlockFields()
'Update MS Word status bar
Application.StatusBar = "PDF saved to DocMan Temp. Now unlocking fields in active document. Please wait..."
'Declare a variable we can use to iterate through sections of the active document
Dim docSec As section
'Loop through all document sections and unlock fields in the specified ranges
For Each docSec In ActiveDocument.Sections
docSec.Footers(wdHeaderFooterFirstPage).Range.fields.Locked = False
docSec.Footers(wdHeaderFooterPrimary).Range.fields.Locked = False
docSec.Footers(wdHeaderFooterEvenPages).Range.fields.Locked = False
Next
End Sub
Sub CPE_ExportAsPDF()
'Update MS Word status bar
Application.StatusBar = "Saving document as PDF. Please wait..."
'Chop up the filename so that we can remove the file extension (identified by everything right of the first dot)
Dim adFilename As String
adFilename = Left(ActiveDocument.FullName, (InStrRev(ActiveDocument.FullName, ".", -1, vbTextCompare) - 1)) & ".pdf"
'Export to PDF with various arguments (here we specify file name, opening after export and exporting with bookmarks)
With ActiveDocument
.ExportAsFixedFormat outPutFileName:=adFilename, _
ExportFormat:=wdExportFormatPDF, OpenAfterExport:=True, _
OptimizeFor:=wdExportOptimizeForPrint, Range:=wdExportAllDocument, _
Item:=wdExportDocumentContent, IncludeDocProps:=True, KeepIRM:=True, _
CreateBookmarks:=wdExportCreateWordBookmarks, DocStructureTags:=True, _
BitmapMissingFonts:=True, UseISO19005_1:=False
End With
'Update MS Word status bar
Application.StatusBar = "PDF saved to DocMan Temp."
End Sub
Try something like the following to get to all fields in the document, header, footer, background and main text:
Sub LockAllFieldsInDocument(poDoc As Document, Optional pbLock As Boolean = True)
Dim oRange As Range
If Not poDoc Is Nothing Then
For Each oRange In poDoc.StoryRanges
oRange.Fields.Locked = pbLock
Next
End If
Set oRange = Nothing
End Sub
Here is another way to do it. It'll select the entire document and then lock all fields, before deselecting everything.
Sub SelectUnlink()
ActiveDocument.Range(0, 0).Select
Selection.WholeStory
Selection.Range.Fields.Unlink
Selection.End = Selection.Start
End Sub
Background: I want to use a specific entered text from a TextBox for the default filename in the SaveAs dialog.
I have implemented the following VBA script in my document, a Word 2010 template .dotm
Sub FileSaveAs()
'for testing
Dim fileName As String
fileName = Me.tb_myTextBox.Value & "_MyFileNameToSave"
MsgBox fileName
'use specific file name in save dialog
With Dialogs(wdDialogFileSaveAs)
.Name = fileName
.Show
End With
End Sub
It works fine, when I run it. I saved the .dotm, closed it and reopened it out from the Windows Explorer (means as "end user").
BUT in this case, means after open the template document as "end user" (so that I can save a new doc out of it and not overwrite the template), the content/value of the TextBox is empty, even if I entered something into it.
So, how can I read out the data of the TextBox in "document mode" of a template?
Presumably, the OP's intention was something along the lines of:
Sub FileSaveAs()
Dim StrNm As String
With ActiveDocument
StrNm = Split(.Shapes(1).TextFrame.TextRange.Text, vbCr)(0) & "_MyFileNameToSave"
'use specific file name in save dialog
With Dialogs(wdDialogFileSaveAs)
.Name = StrNm
.Show
End With
End With
End Sub
where .Shapes(1) identifies the particular textbox Shape object.
how can I read out the data of the TextBox in "document mode" of a template?
Not sure what you mean. This works for me:
create a form:
Private Sub btn_OK_Click()
Dim fileName As String
fileName = tb_myTextBox.Value & "_MyFileNameToSave"
With Dialogs(wdDialogFileSaveAs)
.name = fileName
.Show
End With
End Sub
create a sub to call this form:
Sub FileSaveAs()
UserForm1.Show
End Sub
This is all saved in a template / .dotm.
Now, create a document off of the template (double click the template to launch document off of it). Alt + F8 and run the macro from the template (you may have to select the template from the "Macros in" drop down). Result: my form comes up, I enter a name for the document, press ok, and the Word Save As dialog appears with the name I gave to the document.