Posts tonen met het label Document generation. Alle posts tonen
Posts tonen met het label Document generation. Alle posts tonen

vrijdag 4 november 2011

Wordprocessing Serialization: move content between wordprocessing files

At the moment I’m working on a couple of side projects (which explains why I forgot to close last month off with a funny post). I’ll be blogging about them a bit more in the couple of weeks or months ahead.  A small one-man’s project I’m working on has to do with OpenXml.  For those that have been following my blog, it won’t sound much of a surprise that I’m pretty fond of doing document generation with OpenXml.  I wanted to make it a bit easier though.

A recurring requirement I’ve been confronted with is the copying of content from one document to another.  It can even go so far that you must be able to store this content and be able to inject it into multiple documents at a later moment.  One might think that this is an easy think to do but think again.  If you’re document is crammed with custom style, numbering and tables you’ll soon notice that a simple copy of the paragraphs won’t do the trick.  

So what I’ve done is create a library that allows you to transfer content between 2 wordprocessing documents and keep the format of the table and styles.  Not only can you transfer the content between documents, you can also save the content as a blob to your database or any other file.  The way that you can mark the content that you want to serialize is by bookmarks.  You place two bookmarks in the source document to determin the start and end of the content, and one bookmark in the target document to indicate where to insert the copied content.

So how does it work? There are basically 3 main objects to the library that you need to know about.

  • ContentSerializer
  • ContentDeserializer
  • ContentInserter

ContentSerializer

The content serializer will serialize OpenXmlElements between the two bookmarks that you specify.  Here is a small example:

var memoryStream = new MemoryStream();

IContentSerializer serializer = new ContentSerializer("c:\\temp\\Test.docx");
serializer.SerializeElementsFullBetweenBookmarks(memoryStream,
                                                "profielstart",
                                                "profieleind");

As you can see the ContentSerializer accepts a string that points to the location of where the document resides.  You can then call the SerializeElementsFullBetweenBookmarks.  This method accepts 3 parameters:

  • The stream to which it will serialize to
  • The bookmark indicating the start of the text
  • The bookmark indicating the end of the text

The ContentSerializer will then serialize all OpenXmlElements between the two bookmarks together with their styles and numbering definition.  You can use the data in the stream to save the objects to a database or a binary file.

There is also a SerializeElementsBetweenBookmark function available which will only serialize the paragraphs between two bookmarks.  Yet for this example I want to be able to serialize everything.

ContentDeserializer

The ContentDeserializer will simply deserialize the serialized content into a format usable to inject into a document.  Depending on whether you serialized only the paragraphs or the full content with styles and numbering you can call one of the two following methods:

  • DeserializeContent: This will deserialize only paragraphs
  • DeserializeContentWithNumberingAndStyles: This method will deserialize the paragraphs together with the numbering and styles.

A small demonstration:

IContentDeserializer deserializer = new ContentDeserializer();
var contentWithNumbering = deserializer.DeserializeContentWithNumberingAndStyles(memoryStream);

This method will return an object that contains an IEnumerable of paragraphs, an IEnumerable of styles and a numbering definition. 

ContentInserter

When you have the content deserialized you might want to insert it into another docx file.  The ContentInserter will do just this for you.  All you need to do is provide it with the location of the wordprocessing document and then call the InsertElementsWithNumberingInDocument method to insert the deserialized content at the provided bookmark.

IContentInserter contentInserter = new ContentInserter("c:\\temp\\InsertInDocument.docx");
                contentInserter.InsertElementsWithNumberingInDocument(contentWithNumbering, "Paste");

So the InsertElementsWithNumberingInDocument method accepts the following parameters:

  • An ElementsFull element which contains the deserialized content
  • The name of the bookmark in which it will insert after.

There are a few things that you should take note to:

  • When the document is under revision control, all revisions will be accepted prior to serializing or inserting content!
  • The bookmarks are case sensitive
  • This projects uses the OpenXml Power tools, it’s a cool open source project so be sure to take a look at: http://powertools.codeplex.com/
  • The content that will be serialized between the two bookmarks currently only consists of Parargraphs and all their children (run, text, runproperties, etc.) and tables and all their children (tablecell, etc.).

Some things that I might improve in the future are:
  • Include the serialization of Images
  • Include the serialization of more elements like drawings and graphs
  • Optimize the serialization of the styles and numbering part, at the moment there is no filter

 
Make any adjustments as you like, until next time ;)

woensdag 1 juni 2011

XSLT transform your XML data into an OpenXml wordprocessing document


In this post I’ll explain to you how you can effectively and easily generate data into a OpenXml word-processing document.  I’m going to assume that you have some basic knowledge of OpenXml.  If you’ve been living under a rock for the last 6 years and don’t know what OpenXml is I gladly refer you to this PDFdocument.  It’s one of the best, if not the best, resources out there on OpenXml.

So to give you a basic Idea of what we are going to do I’ve drawn it in a little diagram:

 

We have basically 4 steps:
  1. We take our xml data and if there isn’t an XSD available then we create one
  2. With this XSD we start creating our template with CustomXml tags in them
  3. When the template is finished we run a tool to get a XSLT document that can generate our document
  4. We feed the XML data to the XSLT transformer and let it do its magic
1. Get the XSD 
Actually this step isn’t actually a step but more of a hint.  In the case you don’t have an XSD document for you XML data you can easily generate one with Visual Studio.  Just open up the XML document with Visual Studio and then go to the menu XML and select Create Schema.
 

 
2. Generate the template 
In order to do this I would recommend you to use Microsoft Word 2007 and above.  You COULD do this manually but since I’m very fond of my sanity I won’t do this.  Just create an empty word document in Word and see if you have the Developer ribbon.  If you don’t have the Developer ribbon then check here on how to make it appear.

In the developer ribbon go to Schema and import your XSD as I’ve did in the image.

 
In case you’re retarded: To import the schema you have to click ‘Add schema…’.

 
Select the option Allow saving as XML even if not valid.  I don’t know exactly why this has to be on but I’m guessing it has something to do with cross tagging of the custom XML tags.

Select you schema and then you can start editing you document!  Toggle the structure button in the ribbon next to the schema button.  This will give you a side panel which allows you to select the tags defined in your XSD.   

If you have sections you wish to generate per node then drag a selection around it and select the parent node.  As an example, in the image I will be generating the selected text PER customer.

You can apply this principle also on tables.  Here I generate a row PER product:

 

This is how the total document looks like:


When this is done, save the file as a Word 2003 XML document. Now I see you pondering: “huh wtf, I thought this was XSLT transformation with an OpenXml document???”.  Yes it still is but in disguise, I’ll explain in step 3.

3. Get the XSLT
Well you can do this is plenty of different ways.  If you’re really hardcore then you can write the XSLT yourself.  Send me a postcard from the mental institution when you’ve done this.  

The reason why I saved the OpenXml document as a word 2003 document is because I wanted to have the OpenXml package as a flat structured document.  What do I mean with that?  I want all the parts of the OpenXml package to be slammed into one XML document.  This off course has some disadvantages, performance being one of them.  

Once we have this we can feed this flat XML document to a tool called WML2XSLT .  The usage of this tool is as following:  

WML2XSLT.EXE TheTemplate.xml –o Generator.xslt.   

When the tool launches it gives you the option of selecting a namespace.  Select the namespace of you XSD:

  

After this the tool will have generated an XSLT for you!

4. Result
 All we have to do now is to transform our data XML document by applying the XSLT.  We can do this in code or with Visual Studio by selecting XML => Start XSLT (with/without debugging).  The result in my case looks something like this:

 
Now we have one small issue.  The result we have is an xml document, but we want a docx document so our customer is happy.  Well this is pretty easy to solve and I didn’t know about this since a couple of weeks ago when a colleague bumped into this.  You can create an empty package and add a part inside it of type chunck.  Then guess what this part is going to be?  Yes our flat chuncky document =) 
Here is an excellent link that will guide you through this:

using (WordprocessingDocument myDoc =
    WordprocessingDocument.Open("Test1.docx", true))
{
    string altChunkId = "AltChunkId1";
    MainDocumentPart mainPart = myDoc.MainDocumentPart;
    AlternativeFormatImportPart chunk = mainPart.AddAlternativeFormatImportPart(
        AlternativeFormatImportPartType.WordprocessingML, altChunkId);
    using (FileStream fileStream = File.Open("TestInsertedContent.docx", FileMode.Open))
        chunk.FeedData(fileStream);
    AltChunk altChunk = new AltChunk();
    altChunk.Id = altChunkId;
    mainPart.Document
        .Body
        .InsertAfter(altChunk, mainPart.Document.Body.Elements<Paragraph>().Last());
    mainPart.Document.Save();
}

Voila that’s it.  Nothing to it!  You can find the files here.

Next blogpost will be in a couple of weeks because I’m going on a 2 week trip to rhodos now. Hell yeah! ;)