Here is how I’ve got to create this sample – One of my customer had a similar requirement when I tried to search a sample for him to demonstrate, I couldn’t find one (blame it on my searching capabilities or whatever .. )
While creating a sample there are a few titbits that I came to know -
1) In order to create a valid presentation you’ve got to have following parts - presentation, slide, slideLayout, slideMaster and theme
2) By default when you create a package using Packaging API it’s NOT compressed. To have a compressed package you need to specify compression option in CreatePart. Something like this …
Dim pPart As PackagePart = p.CreatePart(pURI, contentType,CompressionOption.Fast)'orDim pPart As PackagePart = p.CreatePart(pURI, contentType,CompressionOption.Maximum)'orDim pPart As PackagePart = p.CreatePart(pURI, contentType,CompressionOption.Normal)'orDim pPart As PackagePart = p.CreatePart(pURI, contentType,CompressionOption.NotCompressed)'orDim pPart As PackagePart = p.CreatePart(pURI, contentType,CompressionOption.SuperFast)
The real fun part here is – you can have different parts compressed in different CompressionOption (that implies that I can have a few parts compressed and a few parts uncompressed!) – well at least this is what my testing says.
Here is the code snippet (the code is dependent on a few xmlfiles which are attached as a zip)-
Public Class Form1#Region "NamespaceConstants" Private Structure constants Dim dummy Shared presentationmlNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Shared relationshipNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships" Shared corePropertiesSchema As String = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties" Shared docPropsVTypes As String = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" Shared slidePartNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Shared appPartNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Shared themePartNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme" Shared slidelayoutNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Shared slidemasterNamespace As String = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Shared mainpartNamespace As String = "http://schemas.openxmlformats.org/presentationml/2006/main" Shared mainPartContentType As String = "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" Shared slidePartContentType As String = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml" Shared slideLayoutContentType As String = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" Shared appPartContentType As String = "application/vnd.openxmlformats-officedocument.extended-properties+xml" Shared corePartContentType As String = "application/vnd.openxmlformats-package.core-properties+xml" Shared themePartContentType As String = "application/vnd.openxmlformats-officedocument.theme+xml" Shared slidemasterContentType As String = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" End Structure#End Region Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click Using myPackage As Package = Package.Open("C:\somepath\GuineaPig.pptx", FileMode.Create, FileAccess.ReadWrite, FileShare.None) 'Add package parts app,core and doc Dim docPart As PackagePart = CreateBasicPart(New StreamReader("C:\somepath\presentation.xml").BaseStream, myPackage, "ppt/presentation.xml", constants.mainPartContentType, constants.presentationmlNamespace) 'end add 'add other *required* parts slide,slidemaster,theme,slidelayout ' Dim pp() As PackagePart 'add slide part pp = New PackagePart() {docPart} Dim slidePart As PackagePart = CreateExtendedPart(myPackage, "C:\somepath\slide1.xml", "ppt/slides/slide1.xml", constants.slidePartContentType, constants.slidePartNamespace, pp) AdjustPartXML("//p:sldIdLst/p:sldId/@r:id", docPart, constants.slidePartNamespace) 'end add slide part 'add theme part pp = New PackagePart() {docPart} Dim themePart As PackagePart = CreateExtendedPart(myPackage, "C:\somepath\theme1.xml", "ppt/theme/theme1.xml", constants.themePartContentType, constants.themePartNamespace, pp) 'end add theme part 'add slide layout part pp = New PackagePart() {slidePart} Dim slidelayoutPart As PackagePart = CreateExtendedPart(myPackage, "C:\somepath\slidelayout1.xml", "ppt/slideLayouts/slideLayout1.xml", constants.slideLayoutContentType, constants.slidelayoutNamespace, pp) 'end add slide layout part 'add slide layout part pp = New PackagePart() {docPart, slidelayoutPart} Dim slidemasterPart As PackagePart = CreateExtendedPart(myPackage, "C:\somepath\slidemaster1.xml", "ppt/slideMasters/slideMaster1.xml", constants.slidemasterContentType, constants.slidemasterNamespace, pp) AdjustPartXML("//p:sldMasterIdLst/p:sldMasterId/@r:id", docPart, constants.slidemasterNamespace) 'end add slide layout part 'add other *required* parts slide,slidemaster,theme,slidelayout 'add other relationships AddRelationship(slidemasterPart, themePart, constants.themePartNamespace) AddRelationship(slidemasterPart, slidelayoutPart, constants.slidelayoutNamespace) AdjustPartXML("//p:sldLayoutIdLst/p:sldLayoutId/@r:id", slidemasterPart, constants.slidelayoutNamespace) 'end add other relationships End Using Me.Close() End Sub Public Sub AdjustPartXML(ByVal xpath As String, ByVal p As PackagePart, ByVal relationship As String) Dim nt As New NameTable Dim nsManager As New XmlNamespaceManager(nt) nsManager.AddNamespace("p", constants.mainpartNamespace) nsManager.AddNamespace("r", constants.relationshipNamespace) Dim adoc As New XmlDocument(nt) adoc.Load(p.GetStream()) If Not IsNothing(adoc.SelectSingleNode(xpath, nsManager)) Then For Each r As PackageRelationship In p.GetRelationshipsByType(relationship) adoc.SelectSingleNode(xpath, nsManager).Value = r.Id adoc.Save(p.GetStream(FileMode.Create, FileAccess.ReadWrite)) Next r End If End Sub Private Sub AddRelationship(ByVal frompart As PackagePart, ByVal topart As PackagePart, ByVal relationship As String) frompart.CreateRelationship(topart.Uri, TargetMode.Internal, relationship) End Sub Private Function CreateExtendedPart(ByVal p As Package, ByVal filename As String, ByVal uri As String, ByVal contenttype As String, ByVal relationship As String, ByVal relatewith() As PackagePart) As PackagePart Dim xmlDoc As New XmlDocument, xmlFile As New XmlDocument Dim pURI As Uri = PackUriHelper.CreatePartUri(New Uri(uri, UriKind.Relative)) Dim pPart As PackagePart = p.CreatePart(pURI, contenttype) For Each pp As PackagePart In relatewith pp.CreateRelationship(pPart.Uri, TargetMode.Internal, relationship) Next xmlDoc.Load(filename) xmlDoc.Save(pPart.GetStream(FileMode.Create, FileAccess.Write)) xmlDoc = Nothing Return pPart End Function Private Function CreateBasicPart(ByVal fileStream As Stream, ByVal pptPackage As Package, ByVal uri As String, ByVal contentType As String, ByVal relType As String) As PackagePart Dim documentUri As Uri = PackUriHelper.CreatePartUri(New Uri(uri, UriKind.Relative)) Dim documentPart As PackagePart = pptPackage.CreatePart(documentUri, contentType) pptPackage.CreateRelationship(documentPart.Uri, TargetMode.Internal, relType) Dim xmlDoc As New XmlDocument xmlDoc.Load(fileStream) xmlDoc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write)) Return documentPart End FunctionEnd Class
Never mind my naming conventions or using structure as a hack – look at the juice :)
By the way – when you work with Open XML one of the great help is sample documents from OpenXMLDeveloper
Whenever I get more time, I’ll expand this code snippet in a more or less generic sample. Keep watching
Not responsible for errors in content, meaning, tact, or judgment. Live and let live. Toes go in first. I didn't do it. Enjoy.
One of my customer reported a couple of very interesting issues (bugs?) in PowerPoint 2007.
If you have a presentation which has a shape with a gradient and you try to find out the color of each gradient using GradientStops.Color.RGB from .NET, you might get incorrect results. You can also reproduce this issue from VBA with the following steps.
1) Create a new presentation with a single slide.2) Delete textboxes and anything else that's present on the slide.3) Click on Insert->Shapes, select rectangle.4) Right click on the shape, select "Format Shape"5) Click on gradient fill and click close. 6) Hit Alt+F11 to go to VBA editer, if immediate window is not visible, hit Ctrl+G to make it visible.7) If watch window is not visible make it visible by clicking on View -> "Watch window"8) Now, in the immediate window try "?Application.ActivePresentation.Slides(1).Shapes(1).Fill.GradientStops(1).Color"9) Try this for all the gradients, YOU WILL GET DIFFERENT VALUES - EXPECTED RESULT10) Drag-n-Drop "Application.ActivePresentation.Slides(1).Shapes(1).Fill.GradientStops(1).Color" to the watch window (note:don't select "?" while drag-drop)11) Modify the immediate window to "?Application.ActivePresentation.Slides(1).Shapes(1).Fill.GradientStops(2).Color" (changed 1 to 2) 12) Now drag-drop this statement (except "?") to watch window.13) Do this for all the gradients, YOU WILL SEE SAME VALUE FOR ALL THE GRADIENTS - UNEXPECTED14) Now, try steps (8) and (9) again, this time YOU WILL SEE SAME VALUE FOR ALL THE GRADIENTS - UNEXPECTED
The same issue also reproduces with "RulerLevels" collection
After some troubleshooting I found out that using Application.ActivePresentation.Slides(n).Shapes(n).Fill.GradientStops(n).Color.RGB gives correct values when you use it from VBA macro.
Well , after finding this, I did the obvious fwd these issues to the bug database and the so that the concerned teams can take it from here (dev/test etc.)
Now, what can you do right now, especially if you are programming in .NET. How does this finding helps you? It does, if you don't mind a bit patchy solution (Okay .. a workaround if I must say ). You can simply put a small macro in an PPA or PPAM addin (something like ...)
Public Function getGradientStopColor(ByVal iSlide As Integer, ByVal iShape As Integer, ByVal iGS As Integer) As Long getGradientStopColor = ActivePresentation.Slides(iSlide).Shapes(iShape).Fill.GradientStops(iGS).Color.RGB End Function
And call the macro from you .NET code, something like this ...
object[] oParam = { 1, 1, 1 }; object retVal = Globals.ThisAddIn.Application.Run("getGradientStopColor", ref oParam); Debug.WriteLine(retVal.ToString()); oParam[0] = 1; oParam[1] = 1; oParam[2] = 2; retVal = Globals.ThisAddIn.Application.Run("getGradientStopColor", ref oParam); Debug.WriteLine(retVal.ToString()); oParam[0] = 1; oParam[1] = 1; oParam[2] = 3; retVal = Globals.ThisAddIn.Application.Run("getGradientStopColor", ref oParam); Debug.WriteLine(retVal.ToString());
Hold on ..I know, not a great way of doing things, but unfortunately I didn't find anything better.
If you do find something better, please keep me posted.
It's there, Open XML SDK April CTP is released, it's for all of us - the proud office developers :) I was waiting for it! Erica - thanks a lot ... (in fact I was searching for this last week, but couldn't find it ... well, I was a bit early). Also, you might want to look at the future plans (which looks concrete based on the previous experience with the team ... hey - you guys are doing a phenomenal job!). You can download it from here
And - why am I so exited? Because among all the other changes, there is the annotation thing which is super cool, this enables you to write efficient code by minimizing serialization and de-serialization.
That is to say - You can associate arbitrary data with a particular part(think of it as VB 6.0 tag property on steroids). For an example, After opening an Open XML document, you may want to read a part into a System.Xml.Linq.XDocument, query the XDocument object using LINQ to XML, perhaps modify the XDocument, and then serialize the XDocument back into the package. If you need to read the XML from the part, parse it, modify it, and then serialize it back into the package every time that you want to access the XML, your code will not perform as well as if you read the XML from the part only once, then use it as appropriate, and then serialize back into the part only once. If, once having read the XML from the part, you add the XDocument instance as an annotation on the part, you can easily retrieve the annotation instead of rereading the XML each time you need to access it. Annotations allow you to associate any object with an OpenXmlPartContainer (the base class of OpenXmlPart) in a type safe way.
An example code would be -
Module LocalExtensions ' How to create and extension method <Extension()> _ Function GetXDocument(ByVal part As OpenXmlPart) As XDocument Dim xdoc As XDocument = part.Annotation(Of XDocument)() If (xdoc Is Nothing = False) Then Return xdoc End If Using streamReader As StreamReader = new StreamReader(part.GetStream()) xdoc = XDocument.Load(XmlReader.Create(streamReader)) part.AddAnnotation(xdoc) End Using Return xdoc End Function End Module
For more details regarding annotations, have a look at http://msdn2.microsoft.com/en-us/library/cc471941.aspx . Believe me, annotations is really a great thing, Eric also agrees with me
Here is one of the question from one of you regarding the contents on Display Thumbnails For Every Page Of A Document .
===
Hi. I'd like to thank about your EnhMetaFileBits code, it helped me alot. If you don't mind I'd like to ask you something about that. Can you please give me any hints about how to apply it for every page in a Word document? I'd appreciate any info.
Hey man! you are a decent guy - If I were you, I'd shout and say - "THE POST IS INCORRECT! IT DOES NOT EXPLAIN HOW DO I DO IT FOR EVERY PAGE" :)
Thanks for being nicer then me ...
Here is the code that "Actually" gets the thumbnails for every page :
Imports Word = Microsoft.Office.Interop.Word Imports System.IO Public Class Form1 Dim wdApp As Word.Application Dim wdDoc As Word.Document Dim wdSelect As Word.Range Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click wdApp = New Word.Application wdApp.Documents.Open("<DOCNAME>") wdApp.Visible = True Dim emfData As Byte() Dim ms As MemoryStream Dim mf As Imaging.Metafile Dim pn As Long pn = wdApp.ActiveDocument.ActiveWindow.Panes(1).Pages.Count() 'Get the page count For i As Long = 1 To pn wdApp.Selection.GoTo(Word.WdGoToItem.wdGoToPage, Word.WdGoToDirection.wdGoToAbsolute, i) ' Go to the next page wdApp.ActiveDocument.Bookmarks("\Page").Range.Select() 'Select using the magic bookmark "\page" Debug.Print("Selected " & i & " page") emfData = wdApp.Selection.EnhMetaFileBits 'Get the metafile bits ms = New MemoryStream(emfData) mf = New Imaging.Metafile(ms) mf.Save("c:\upload\page" & i & ".emf") ' Save Debug.Print("Saved " & i & " page") Next i Debug.Print("Done!") End Sub End Class
Here is a quick tip -
Although you can't get/modify the text from a WordArt Shape using “oDocument.Shapes(n).TextFrame.TextRange.Text”, but we can modify the text using “oDocument.Shapes(n).TextEffect.Text”.
Sure, I know there are many ways of doing it - You can save it as HTML and then read the HTML file or you can use the HTMLProject items from the Word OM or you can just select all the contents of the file and save it to clipboard and read it from there.
But, I am telling you a way other then the the usual - big deal ? depends ..
My situation was -
I didn't want to save the file to a temp location as HTML as in that case my active document actually gets modified to HTML. Although, I can do a "Save As" Under a different name, but even in that case my current Active Document will be changed to HTML. In this case (2nd case) if you've saved the original doc as document previously, then at least that document is intact, but the only problem is now if you want that doc back, you'll need to reopen it.
Depending on a lot of things I might, or might not be okay with this approach. Hence ruled out ...
HTML Project items is removed in Word 2007, so that's no option for me ...
Clipboard option is possible, but I don't like the idea of using clipboard from my custom code (seen a lot of issues on it ...) So I thought of using a different approach which is based on IDataObject
Here is the code -
using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Word = Microsoft.Office.Interop.Word; using System.Reflection; using System.Runtime.InteropServices; using COM = System.Runtime.InteropServices.ComTypes; namespace IDataObjectFromWord { public partial class Form1 : Form { [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] private static extern IntPtr GlobalLock(HandleRef handle); [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] private static extern bool GlobalUnlock(HandleRef handle); [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)] private static extern int GlobalSize(HandleRef handle); Word.Application wdApp; Word._Document wdDoc; object o=Missing.Value; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { wdApp= new Word.Application(); wdDoc=wdApp.Documents.Add(ref o,ref o, ref o, ref o); wdDoc.Range(ref o, ref o).Text = "Hello World ..."; wdApp.Visible = true; COM.IDataObject dt = (COM.IDataObject)wdDoc; COM.IPersistFile pp = (COM.IPersistFile)wdDoc; COM.FORMATETC format= new COM.FORMATETC(); COM.STGMEDIUM stgmedium= new COM.STGMEDIUM(); pp.Save(@"C:\upload\Something.doc",false); format.cfFormat = (short)DataFormats.GetFormat(DataFormats.Html).Id; format.dwAspect = COM.DVASPECT.DVASPECT_CONTENT; format.lindex = -1; format.tymed = COM.TYMED.TYMED_HGLOBAL; stgmedium.tymed = COM.TYMED.TYMED_HGLOBAL; stgmedium.pUnkForRelease = null; dt.GetData(ref format, out stgmedium); IntPtr pointer = stgmedium.unionmember; HandleRef handleRef = new HandleRef(null, pointer); byte[] rawArray = null; try { IntPtr ptr1 = GlobalLock(handleRef); int length = GlobalSize(handleRef); rawArray = new byte[length]; Marshal.Copy(ptr1, rawArray, 0, length); } catch (Exception exp) { System.Diagnostics.Debug.WriteLine("HtmlFromIDataObject.GetHtml -> Html Import threw an exception: " + Environment.NewLine + exp.ToString()); } finally { GlobalUnlock(handleRef); } //return rawArray; pp.Save(@"C:\upload\Something.doc", false); System.Diagnostics.Debug.WriteLine(Encoding.UTF8.GetString(rawArray)); }
Yes ..really thanks everybody, people who were "for" and people who were "not for".
I believe that everything that happened in this one year helped in the improvement of the spec.
The winner is not only Open XML, You and Me also won in the process. I am with Miguel de Icaza - OOXML: The Wins
I am really looking forward to the next set of cases of Open XML. Already a lot of the cases we are handling are Open XML related and obviously you'll be seeing a lot of Open XML content on this blog gradually.
Putting it simply, this news has a great positive impact to me and to everyone .. so guys, cheer up!
Details are already covered by a lot of people who can do it far better then me and I'll leave it to them ...
IS29500 The Process Challenge - A Predictable PathISO: ISO/IEC DIS 29500 receives necessary votes for approval as an International Standard Final: ISO/IEC DIS 29500 receives necessary votes for approval as an International Standard
Ever wondered how can you do a "Save Copy As" in word? Or why it's not there? well ... I don't know about why it's not there, but I can tell you how to do it using custom code
To do it, the first thing you need to know about is - IPersistFile interface, this interface provides methods that permit an object to be loaded from or saved to a disk file, rather than a storage object or stream. As this is one of the compound document interfaces, word implements it.
Getting back to the theory, whenever any component implements an interface it has to implement all of it's methods, which in turn gets added to the vtable of the component, whenever you want to use this interface from an external client you need to obtain a it's pointer, then you can call any of the methods from this interface.
Here is a sample code in C#. Yes! it's a simple code, the only reason is System.Runtime.InteropServices.ComTypes Namespace which contains methods that are defintions of COM functions for managed code including IPersistFile.
If this was not available, then I might have been required to play a lot of "Marshal." and/or Win32API games to get this thingy working -
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Word = Microsoft.Office.Interop.Word; using System.Reflection; using System.Runtime.InteropServices; using COM = System.Runtime.InteropServices.ComTypes; namespace IPersistFileFromWord { public partial class Form1 : Form { Word.Application wdApp; Word._Document wdDoc; object o=Missing.Value; public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { wdApp= new Word.Application(); wdDoc=wdApp.Documents.Add(ref o,ref o, ref o, ref o); wdDoc.Range(ref o, ref o).Text = "Hello World ..."; wdApp.Visible = true; COM.IPersistFile pp = (COM.IPersistFile)wdDoc; pp.Save(@"C:\upload\Something.doc",false); } } }
Enjoy and take care ...