wordpress hit counter
Working with Open XML in Silverlight - OpenXML Developer - Blog - OpenXML Developer
Goodbye and Hello

OpenXmlDeveloper.org is Shutting Down

There is a time for all good things to come to an end, and the time has come to shut down OpenXmlDeveloper.org.

Screen-casts and blog posts: Content on OpenXmlDeveloper.org will be moving to EricWhite.com.

Forums: We are moving the forums to EricWhite.com and StackOverflow.com. Please do not post in the forums on OpenXmlDeveloper.org. Instead, please post in the forums at EricWhite.com or at StackOverflow.com.

Please see this blog post for more information about my plans moving forward.  Cheers, Eric

Working with Open XML in Silverlight

Working with Open XML in Silverlight

  • Comments 12


By Chris Klug

 

I have spent a lot of time working with Silverlight applications lately and I am pretty sure that the interest for the technology will not shrink, but instead grow. Clients have realized that good looks are important and Silverlight offers a lot more esthetical freedom than HTML. Developers have realized that Silverlight development is a nice and structured experience. There are however downsides to the technology as well. What if a customer wants to save some of the information on the screen? Well, in HTML you can either save the page to your hard drive or just print it. In Silverlight you can not.

 

The current version (3.0), does not have print support so it is impossible to just save the page you are looking at. So how do we give the customer what he or she wants? I have seen a couple of common approaches to the problem. One involves using JavaScript to pop-up a new window then writing the information to that. The other involves posting all the information back to the server and then have the server generate a document that the client can then download. Both of these methods work, but can be very cumbersome and lack flexibility, especially now that Silverlight starts going offline.

 

One possible solution is obviously to use Open XML. Open XML has the great benefit of giving the customer a way to store the required information at the same time as being relatively easy for the developer to work with. The biggest problem with this approach is that the Open XML SDK is not available for Silverlight developers. Having realized this, I decided to create a small library that would ease the burden of creating Open XML based documents. The goal was to create a tiny library with few pre-built features, and instead being flexible and easy to modify and extend.

 

Intro

 

This sample aims to show how to use my library to create Word documents as well as how to extend it to support new features. It will also show how the existing library can create simple Word documents with text and styles, as well as how you can add support for features that are missing. In this case I will focus on how to implement support for headers and footers.

 

Problems and Solutions

 

After deciding to build this library, I realized that there was a pretty big problem standing between me and my library.  Silverlight does not support compressing stream using Zip compression.  I had assumed this was available because Silverlight itself is zipped. Unfortunately only decompressing using a zip file is available. There are however a few projects on the web that can solve this so I decided to use the Silverlight port of the commonly used SharpZipLib.  This makes zipping files an easy task.

 

 Architecture

 

Next I wanted to figure out how to build the library so that it would be extendable in an easy way. I decided to use an architecture closely resembling OpenXML’s native architecture. The packaging architecture in Open XML is supported by 2 abstract base classes called Package and PackagePart.

 

The Package class is responsible for handing the different parts of the document as well as saving the document. From an Open XML perspective, the Package class handles the [Content_Types].xml and the _rels/.rels files. The Package class contains a list of Parts and a list of Releationships.

 

The PackagePart is supposed to be used as the base class for any “part” you want to add to the package. It can be an XML based part as the case of the DocumentPart and the StylesPart, but it can also be a “binary” part such an image. The PackagePart contains a couple of abstract properties called Name, ContentType and RelationshipType. The Name is the name of the physical file in the package. It should always start with a slash, for example “/word/document.xml”. The ContentType is used by the Package to define what is in the part and the RelationshipType is used when the part is being added as a reference to another part. Besides those properties, it contains a collection of namespaces as well as a collection of relationships. It also contains a couple of very important methods, such as Save, SaveContent and CreateElement<T>. The CreateElement<T> is extremely important. It works as a factory method. Any “element” added to a part needs a reference to the “parent” part. This is automatically set when creating elements by using the generic method CreateElement<T>.

 

If you know your Open XML, the way that the different parts fit together should seem very familiar as I have tried to have it mimic the physical and XML structure as much as possible.

 

Document Content

 

How do I handle all of the XML elements, and all the information that is to be placed in the parts?  Well, if we start by looking at the PackagePart, it will be pretty clear how to do it. The PackagePart has a Save method that will be called from the PackagePart when the Package is saved. The default implementation of that method creates a new “physical” and “attaches” an XmlWriter to the file stream. It then passes that XmlWriter to the SaveContent method. Even though the Save method is virtual, unless you need to save something else than XML, I recommend overriding the SaveContent method. This will first of all make things easier, but it will also keep the rest of the Save method going as expected. The Save method also happens to be responsible for saving the PackageParts releationships, so if saving an XML based PackagePart, leave that method alone and override the SaveContent method instead.

 

In the SaveContent method, we can save whatever XML we need however the base class does not help us with this.   To see how to use it, we need to move on and look at an actual implementation.   As the only pre-built implementation is the WordDocument and its corresponding parts, we will need to look there.

 

The WordDocument class inherits from the Package part as it is an Open XML package. The WordDocument contains 5 “fixed” PackageParts – one AppPart, one CorePart, one FontTablePart, one StylesPart and finally, the very crucial DocumentPart. The AppPart and CorePart contain “extra” information about the package, that is used by for example Microsoft Word and have properties like ApplicationName and Company. So the constructor starts off by creating instances of these classes and add them, to the Package by calling Parts.Add(). It then adds relationships to them using the Relationships.Add() method.

 

DocumentPart

 

Let’s ignore most of these Parts and focus on the most important one, the DocumentPart. This part contains all the content of the document, and gives us a good view on how we are supposed to use the framework when creating our parts.

 

The DocumentPart in itself is quite simple as it lets the base class, PackagePart, to do all the “heavy” lifting. All it has to do is implement the abstract properties from the base class, override the SaveContent method and add whatever extra properties it needs. In the case of the DocumentPart, it only needs one extra property called Sections. The Sections property is of type SectionCollection and as you can see, the implementation mimics the XML used in the Open XML document that is to be created.

 

The DocumentParts implementation of the SaveContent method is quite simple as well. Each piece of the package is only responsible for saving itself. The DocumentParts SaveContent implementation writes a <document> tag and then calls the base class’ AddNamespaces to add the required namespaces. After the namespaces have been added to the XmlWriter, it creates a <body> element before looping through each of the Sections in the Sections property and calling its Save method. That is generally how our “elements” will work. They will be responsible for saving themselves and then looping through any “contained” elements and ask them to save themselves.

 

Element base classes

 

To help out with this, each “element” in the document should inherit from one of two base classes, OXMLElement or OXMLContainerElement<T> (which in turn inherits from OXMLElement). The OXMLElement class, gives any inheriting class some help, and is also the reason for having to use PackagePart.CreateElement<T>() to create your elements. It has a Part property that gives all elements access to the “parent” PackagePart, as well as 2 abstract properties called NodeName and NodeNamespace. The NodeName is the name of the element that the class represents, for example “r” in the case of the Run class. The NodeNamespace is the namespace that the element is defined in. It is, as with all namespaces in the library, exposed as a KeyValuePair<string,string> property. When it comes to the persistence of the element, it resembles the PackagePart a lot. It implements 2 virtual methods called Save and SaveContent. The Save method is responsible for saving the entire element, including the elements element tag. The default implementation creates the initial element, with the right namespace definition, and then calls SaveContent, where you save the content of the element.

 

So what is the OXMLContainerElement<T> all about? Well, I soon realized that a lot of the elements used, contain a list of other elements. So the OXMLContainerElement<T> declares a protected List<T> property to store these. It isn’t more complicated than that...

 

Creating HeaderPart and FooterPart

 

But instead of looking at the base classes further, let’s see if we can’t extend the existing functionality and see how to use the library for real. One of the features available in Word that is not available by default in the library is the support for headers and footers. And since this is a fairly commonly used functionality, let’s add it. Unfortunately this cannot be added nicely as an extension as it needs to be added “inside” some of the pre-existing classes. Luckily enough, the library’s source is available, so we can just go in and make any change we need.

 

Let’s start by creating the implementing the HeaderPart and FooterPart classes. They should inherit from PackagePart, as they are their own files in the package. They should take the name of the part as a parameter in the constructor and expose this in the Name property. They also need to override the ContentType and the RelationshipType properties. Instead of overriding these and use hard coded strings, I add the corresponding strings as static properties on the RelationshipTypes and ContentTypes classes and use those.

 

public class HeaderPart : PackagePart

{

    string _name;

 

    public HeaderPart(string name)

    {

        _name = name;

    }

 

    ....

 

    protected internal override string Name

    {

        get { return _name; }

    }

    protected internal override string ContentType

    {

        get { return ContentTypes.WordprocessingML.Header; }

    }

    protected internal override string RelationshipType

    {

        get { return RelationshipTypes.Header; }

    }

}

 

Next I need to define what the user of the class can actually do with it. In the case of the HeaderPart and FooterPart, they contain Paragraphs. So I expose a property of type List<Paragraph> called Paragraphs.

 

List<Paragraph> _paragraphs = new List<Paragraph>();

public List<Paragraph> Paragraphs

{

    get { return _paragraphs; }

}

 

Finally I need to make sure that it is saved correctly. Luckily, the HeaderPart and FooterPart are both just XML documents, so I can stick with just overriding the simple SaveContent() method, leaving the creation of the actual file and the persistence of the relationships to the base class. Event implementing this, the toughest of the things to implement, is quite simple. Just write the correct element using the writer and then pass control to the base class to write the required namespaces. When control is returned, just iterate through each of the Paragraphs in the Paragraphs property and have them save themselves to the writer.

 

protected override void SaveContent(XmlWriter writer)

{

    writer.WritePrefixedStartElement("hdr", Utils.Namespaces.WordprocessingML.Main);

    AddNamespaces(writer);

 

    foreach (var p in Paragraphs)

    {

        p.Save(writer, null);

    }

 

    writer.WriteEndElement();

}

 

The code snippet might however need two small clarifications. First of all, the XmlWriter class does not include a method called WritePrefixedStartElement(), it is an extension method declared in the project. Andthe second thing that might need clarification is the second parameter to the Paragraph.Save() method. The Paragraph class is somewhat special due to the way that section information is nestled into their elements. By passing null as the second parameter, we get the default save implementation.

 

That’s it. That’s all that is needed to create those two classes. I have only shown the code for the HeaderPart, but the FooterPart is identical except for the element name used in the SaveContent() method.

 

Next up, we need to get this into the actual document somehow. Headers and footers are declared in the sectionPr element, which in the library is represented by the Section class. So let’s open the Section.cs file and see how we can add the header and footer.

 

Adding the header and Footer to a Section

 

As the HeaderPart and FooterPart are PackageParts, they will be added to the Package and in return we get a Relationship instance. So to enable adding a header and footer to a Section, I expose two new properties of type Relationship, called HeaderReference and FooterReference. In the setter, I add them to the DocumentParts Relationship collection by calling Part.Relationsips.Add(). But before that, I make sure to remove any previously added Relationship.

 

Relationship _headerReference;

public Relationship HeaderReference

{

    get { return _headerReference; }

    set

    {

        if (_headerReference != null)

        {

            Part.Relationships.Remove(_headerReference);

        }

        _headerReference = value;

        Part.Relationships.Add(_headerReference);

    }

}

 

Relationship _footerReference;

public Relationship FooterReference

{

    get { return _footerReference; }

    set

    {

        if (_footerReference != null)

        {

            Part.Relationships.Remove(_footerReference);

        }

        _footerReference = value;

        Part.Relationships.Add(_footerReference);

    }

}

 

Since the header and footer is added using a relationship, I need to make sure that the “relationship namespace” is added to the DocumentPart when saved. So in the Section’s constructor, I make sure to add it to the Namspaces collection.

 

Part.Namespaces.Add(Namespaces.Relationships);

 

Finally, we need to make sure that the footer and header is added to the XML. As I mentioned before, the Paragraph and Section classes are somewhat entangled, and because of this, we will not modify a Save or SaveContent method, but the WriteSection method. The change is quite simple though. Just verify that there is a header/footer before trying to add it. Then use the XmlWriter to add a headerReference/footerReference element. Then add two attributes, one called “type” and one called “id”. Just remember that the “id” attribute is declared in the “relationships namespace”.

 

...

if (HeaderReference != null)

{

    writer.WritePrefixedStartElement("headerReference", NodeNamespace);

    writer.WriteAttributeString(NodeNamespace.Key, "type", NodeNamespace.Value, "default");

    writer.WriteAttributeString(Namespaces.Relationships.Key, "id", Namespaces.Relationships.Value, HeaderReference.ID);

    writer.WriteEndElement();

}

 

if (FooterReference != null)

{

    writer.WritePrefixedStartElement("footerReference", NodeNamespace);

    writer.WriteAttributeString(NodeNamespace.Key, "type", NodeNamespace.Value, "default");

    writer.WriteAttributeString(Namespaces.Relationships.Key, "id", Namespaces.Relationships.Value, FooterReference.ID);

    writer.WriteEndElement();

}

...

 

Using the functionality in Silverlight

 

That’s it, implementation complete. All we need to do now is to try it out in a Silverlight application. So let’s create a simple test application. It needs four textboxes and a save button. I have added mine in a grid that looks like this:

 

<Grid Width="500" Height="500">

    <Grid.RowDefinitions>

        <RowDefinition Height="70"/>

        <RowDefinition Height="45"/>

        <RowDefinition/>

        <RowDefinition Height="70"/>

        <RowDefinition Height="50"/>

    </Grid.RowDefinitions>

    <TextBox x:Name="Header" TextWrapping="Wrap" Margin="0,0,0,10"/>

    <TextBox x:Name="Headline" Grid.Row="1" TextWrapping="Wrap" Margin="0,0,0,10"/>

    <TextBox x:Name="Document" Grid.Row="2" TextWrapping="Wrap" AcceptsReturn="true"/>

    <TextBox x:Name="Footer" TextWrapping="Wrap" Grid.Row="3" Margin="0,10,0,0"/>

    <Button Grid.Row="4" Content="Save" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="20,10" Click="Button_Click"/>

</Grid>

 

Giving us an application that looks like this:

 

 

As you might understand from the names given to the textboxes, there will be a header, footer, headline and some content.

 

But the real interesting part is obviously what to do in the code behind. I start by adding an event handler in the code behind. The handler starts by creating and configuring a new SaveFileDialog, which it then shows to the user. If the user doesn’t cancel out of it, it goes on creating the document.

 

private void Button_Click(object sender, System.Windows.RoutedEventArgs e)

{

    SaveFileDialog dlg = new SaveFileDialog();

    dlg.Filter = "Word Document (.docx)|*.docx|Zip Files (.zip)|*.zip";

    dlg.DefaultExt = ".docx";

    if (dlg.ShowDialog() == true)

    {

        ...

    }

}

 

If you happen to be an MVVM fan, like me, I have to disappoint you by saying that this has to be done in code behind. We are not allowed to open SaveFileDialog in anything else than the code behind, and even that is sometimes tricky.

 

Next, a WordDocument is instantiated, and its ApplicationName, Creator and Company set. Next, I start building the document, one part at the time. I start by creating a footer and a header by calling CreateHeader() and CreateFooter(). I then add those parts to the document and use the returned Releationships to set the first Section’s HeaderReference and FooterReference.

 

WordDocument doc = new WordDocument();

doc.ApplicationName = "SilverWord";

doc.Creator = "Chris Klug";

doc.Company = "Intergen";

 

HeaderPart header = CreateHeader(doc.Document);

FooterPart footer = CreateFooter(doc.Document);

Relationship relHeader = doc.AddPart(header);

Relationship relFooter = doc.AddPart(footer);

doc.Document.Sections[0].HeaderReference = relHeader;

doc.Document.Sections[0].FooterReference = relFooter;

 

The CreateHeader() and CreateFooter() methods look like this:

 

private HeaderPart CreateHeader(DocumentPart doc)

{

    HeaderPart header = new HeaderPart("/word/header.xml");

    Paragraph p = doc.CreateElement<Paragraph>();

    WML.Run r = doc.CreateElement<WML.Run>();

    WML.Text t = doc.CreateElement<WML.Text>();

    t.Content = Header.Text;

    r.Content.Add(t);

    p.Runs.Add(r);

    header.Paragraphs.Add(p);

    return header;

}

 

private FooterPart CreateFooter(DocumentPart doc)

{

    FooterPart footer = new FooterPart("/word/footer.xml");

    Paragraph p = doc.CreateElement<Paragraph>();

    WML.Run r = doc.CreateElement<WML.Run>();

    WML.Text t = doc.CreateElement<WML.Text>();

    t.Content = Footer.Text;

    r.Content.Add(t);

    p.Runs.Add(r);

    footer.Paragraphs.Add(p);

    return footer;

}

 

Next, I want to format my headline a little differently. Being a developer, I am supposed to have no design knowledge at all, so to strengthen this generalization, I will use Comic Sans. Adding a style is a two step procedure. First, you need to add the selected font to the documents font table. I have broken this step into a method of its own, that looks like this:

 

private FontReference AddComicSansFontDefinition(WordDocument doc)

{

    doc.FontTable.CreateElement<FontDefinition>();

    FontDefinition fontDefinition = doc.FontTable.CreateElement<FontDefinition>();

    fontDefinition.Name = "Comic Sans MS";

    fontDefinition.Panose1 = "030F0702030302020204";

    fontDefinition.CharSet = "00";

    fontDefinition.Family = FontFamilyEnumeration.Script;

    fontDefinition.Pitch = FontPitchEnumeration.Variable;

    fontDefinition.Signature.UnicodeSignature0 = "00000287";

    fontDefinition.Signature.CodePageSignature0 = "0000009F";

    return doc.FontTable.AddFont(fontDefinition);

}

 

When doing this, you get a FontReference back. This can then be used when creating a style that is to be added to the documents styles part. Styles come in two forms CharacterStyle and ParagraphStyle. In my case I need a CharacterStyle as I want to control the way my text looks. Each style needs to be given a unique name that is used when referencing them in the document. I create my style using this method:

 

private WML.Style AddTitleStyle(WordDocument doc,FontReference font)

{

    CharacterStyle style = doc.Styles.CreateElement<CharacterStyle>();

    style.Id = "TitleStyle";

    style.Name = "Title Style";

    style.RunProperties.FontSize = 30;

    style.RunProperties.IsBold = true;

    style.RunProperties.Font.ComplexScript = font;

    style.RunProperties.Font.HighAnsi = font;

    style.RunProperties.Font.ASCII = font;

    style.RunProperties.Font.EastAsia = font;

    doc.Styles.AddStyle(style);

    return style;

}

 

Next I need to add the actual headline. This is quite simple, but requires a couple of classes. The text to be shown needs to be added to an instance of Text. That instance in turn needs to be added to an instance of Run, which in turn can be added to a Paragraph in a Section. Luckily, we already have a Section and a Paragraph in the document since this is added when the document is created. Creating a headline with the defined style looks like this:

 

private void AddDocumentHeadline(DocumentPart doc, WML.Style style)

{

    WML.Run r = doc.CreateElement<WML.Run>();

    Text t = doc.CreateElement<Text>();

    t.Content = Headline.Text;

    r.Content.Add(t);

    r.Properties.Style = style;

    doc.Sections[0].Paragraphs[0].Runs.Add(r);

}

 

I do the same thing with the actual document content. I split the text on carriage return and add the text in runs and paragraphs as needed.

 

Finally, we need to save the document at the location selected by the user. Since the document is a compressed collectionof files, we can’t just tell the document to save itself to a Stream. Well, we could if I had implemented it like that. But for certain reasons, including some Silverlight 4 ideas, I have decided to wrap the Stream in an object that implements the IStreamStorage interface. Luckily, the library already contains an implementation called ZipStreamStorage. It takes a Stream and handles all the Zip compression needed. It also implements IDisposable, so we can simply use it like this:

 

using (IStreamStorage storage = new ZipStreamStorage(dlg.OpenFile()))

{

    doc.Save(storage);

}

 

This should result in a well working Open XML document that includes a header and footer, as well as a formatted title and some simple text. The complete source code is available below.

 

 Outro

 

For more details about the library, take a look at my blog http://chris.59north.com. The blog also have a blog post outlining how to add image support to your word documents. Currently the blog only contains that one “extension” to the framework, but hopefully I will be adding more extensions over time.

 

I also want to mention that when I start using the library in more projects, I will probably fix issues and add features. So before you start using it, I recommend having a look on my blog for a potentially updated library. If you have any questions, feel free to ask them on my blog.

Attachment: Source and Binaries.zip
Page 1 of 1 (12 items)