Wednesday, July 8, 2009

How to get around OpenXML error: Can not replace the OpenXmlElement "newChild" because it is part of a tree

I was recently working on a MS Word doc generation project and kept getting this error.

It took me a while to figure out the source of the problem, but it made total sense...
When you are a building a document tree (example below), you are creating new elements like


var myRun = new Run(new Text("reusable text"));

var myParagraph = new Paragraph();

myParagraph.AppendChild(myRun);



The 3rd line above ties in that Run object into the Paragraph object, so if you try to reuse the myRun object by appending it somewhere else - you will get this error.

I also tried creating a new Run() object using the old one as the object initializer:

var myRun2 = new Run(myRun);



That will throw the same error.
Why? Because the above line copies instantiates myRun2 with the reference of the myRun object so when you try to Append it elsewhere, you will get the error because it's technically the same object.

Workaround
There is a very handy property that most OpenXML elements have - OuterXml. The other handy thing about it - it's in most class constructors so it can be used to instantiate an object.
In lamens terms, instead of

var myRun2 = new Run(myRun);


try

var myRun2 = new Run(myRun.OuterXml);



Sample tree:
  • SdtBlock
  • - Paragraph
  • ----Run
  • ------Text
  • ----Run
  • ------Break
  • ----Run
  • ------Text

Monday, June 1, 2009

10 Best Practices For Building SharePoint Solutions

Came across an excellent article in MSDN Magazine:

10 Best Practices For Building SharePoint Solutions

It's a must read if you are looking to deploy custom Sharepoint applications into your farm.

Wednesday, March 18, 2009

Sharepoint

I have added Sharepoint as an additional topic that I will blog about.

I have been doing a lot more Sharepoint development and integration at work, and it's actually sparking an interest.

So, stay tuned.

Sunday, February 22, 2009

OpenXML & Word Doc Manipulation using Sharepoint Data

Call me a lousy blogger but there is no whatisluxury.com code in this post because I've been busy refactoring it. That's not the only reason. I have been involved in a lot more Sharepoint development at work lately - moreso than .Net, so I've been kind of busy reading up SDKs and playing with APIs.

On that note, I got a chance to get my hands dirty with some OpenXML 2.0. The cool thing about that was that it was @ Microsoft. Not only did I get to play with it, but met the guys that developed parts of it and even wrote 2,000 of the 6,000 pages of documentation - Tristan Davis and Zeyad Rajabi - who helped me a great deal.

The story that I worked on involved gathering data from Sharepoint lists (some of it fed from the BDC(business data catalog)) and using OpenXML to populate a Word template. The real world scenario behind it is at my company, like I'm sure at a lot of other companies, we have administrative assistants who end up generating the documents for meetings. They just sit there copying and pasting data from various data sources and reports into this document. And there are as many documents generated as there are assistants multiplied by weeks in the year. Seriously - these people end up working 60 hour workweeks.

The program utilizing OpenXML to generate these documents for them is actually pretty simple. I had some issues deploying it as a web app onto Sharepoint because of various little issues that arose along the way (running sharepoint on a 64-bit windows 2008 server w/ IIS 7 - some assemblies were 32-bit, so couldn't get it to run; other times, there were IIS7 issues. Note to self: improve knowledge of IIS7). Because of that, I wrote it as a WinForms app.

The interface is pretty boring - it has 2 listboxes.
The first listbox contains key data - in this case, a list of product names. This field is used as a foreign key to relate it to a bunch of other lists - sales data list, product manager list, and even a Sharepoint picture library.
The second listbox contains a list of template documents from a document library that was created.
There is also a textbox for indicating what the new filename is going to be once the document is generated, and a button that generates the document.

So how does this all work?
First of all, none of this would be possible if Microsoft hadn't created the new .docx, .xlsx, .pptx formats in Office 2007. As much as I cursed it for doing so - I have seen the light last week. The new format is purely XML based. Hence, OpenXML allows us to manipulate the hell out of our documents without ever opening up Microsoft Word.

The one caveat is that Microsoft Word's Markup Language (WordML) is a bit cumbersome to learn right off the bat. The good news is that with a bit of blog reading from Brian Jones on OpenXML and an extremely powerful tool that comes with the SDK called DocumentReflector, you will be generating documents in no time. Here is a video by Alistair Spears that gives a demo of this wonderful tool: [http://video.msn.com/video.aspx/?vid=d50a0a13-836b-4849-9bbc-4c9134a63754&ifs=true&fr=msnvideo&mkt=en-US&from=writer]

So, how did I get data in from sharepoint lists into a word document? Let's take a look at the process step-by-step.

Traversing Sharepoint Lists/Doc Libraries/Picture Libraries
1. The first thing you will need is to connect to a sharepoint list. The code is the easy part - it's just 2 lines:

//declarations
protected SPListItemCollection product_owner_items;
protected SPListItemCollection template_doc_items;
protected SPListItemCollection marketing_info_items;
protected SPPictureLibrary product_model_pics;
protected SPSite mysite;
protected SPWeb myweb;
mysite = new SPSite("http://izmqdp6452022/Docs/Lists/Product%20Owners/AllItems.aspx");
myweb = mysite.AllWebs["Docs"];

SPSite's constructor can actually determine what site you are connecting to in your Sharepoint farm based on the URL passed in. You just have to make sure that whichever user your app is running as, must have access to these libraries and to the Sharepoint site.

Here is the code used to get my Sharepoint list objects ready for access:
private void initialize_sharepoint_lists()
{
  product_owner_items = mysite.AllWebs["Docs"].Lists["Product Owners"].Items;
  marketing_info_items = mysite.AllWebs["Docs"].Lists["Marketing Info List"].Items;
  template_doc_items = mysite.AllWebs["Docs"].Lists["Documents"].Items;
  product_model_pics = (SPPictureLibrary)mysite.AllWebs["Docs"].Lists["Product Model Images"];
}
In this case, "Docs" is the name of the sharepoint site and the Lists indexers contain the names of the libraries that I am working with in sharepoint.

Once I have the data I need to be inserted into the Word document, I need to create a template in Microsoft Word. This is the easiest part of the operation. You simply open up Microsoft Word, enable the Developer toolbar by right-clicking on the ribbon and insert a text-box field wherever you will have data inserted. Make sure you assign an alias to the text-box - this is how you will access your placeholder for insertion.

Before attempting this exercise, you should find a sample Word 2007 document and open it in DocumentReflector so that you can get an idea of how the XML is formatted because the code below can get confusing REAL quickly if you don't.

The fields that you created using the developer tab can be accessed one of two ways. If the field is on a new line, it will live in a <SdtContentBlock> element. If the field is on the same line as some text, say, a label, it will live in a element. Now, the trick is to locate these elements and pass the data into them.

Here is the method that will take a string, the alias of the field you are manipulating, and a WordProcessingDocument object which contains the open word document. This method will modify a SdtRun block - the element that lives on the same line as other text:

 private void populate_run_control(WordprocessingDocument wordDoc,string alias, string value)
        {
            var contentControls = wordDoc.MainDocumentPart
                            .Document
                            .Descendants()
                            .Where(s => s.SdtProperties.GetFirstChild().Val.Value == alias);
 
            if (contentControls.Count() != 0)
            {
                var sdt = new SdtContentRun(
                           new Run(
                                new RunProperties(
                                    new RunStyleId() { Val = "PlaceholderText" }),
                                new Text(value)));
 
                contentControls.First().SdtContentRun.RemoveAllChildren();
                contentControls.First().SdtContentRun.InsertAt(sdt, 0);
 
            } 
        }

The method below has the same task in mind, but modifies an SdtContentBlock element that starts on a new line. Another difference is that it takes in an html Table object, which can be easily formed just like you are used to in ASP.Net/C# - Table, TableRow [tr], TableCell, etc. This could be as simplistic or as complex as you want.

        private void populate_block_table(WordprocessingDocument wordDoc, string alias, Table value)
        {
            var contentControls = wordDoc.MainDocumentPart
                            .Document
                            .Descendants()
                            .Where(s => s.SdtProperties.GetFirstChild().Val.Value == alias);
 
            if (contentControls.Count() != 0)
            {
                var sdt = new SdtContentBlock(
                           value);
 
                contentControls.First().SdtContentBlock.RemoveAllChildren();
                contentControls.First().SdtContentBlock.InsertAt(sdt, 0);
 
            }
        }

I won't bore you with the rest of the code used to open files into MemoryStreams or save them, but if you would like to see the inner workings anyway, I recommend taking a look at Brian Jones's blog post about Office Extensibility: [http://blogs.msdn.com/brian_jones/archive/2009/01/19/pushing-data-from-a-database-into-a-word-document.aspx]

Don't hesitate to comment and let me know if you have any questions.


Thursday, January 22, 2009

Back from the Dead

For a blogger, I've bit the dust in terms of frequent posts. However, I would rather post useful stuff once a month (or two) than post anything for the sake of posting.

Having said that, I know I haven't posted in a while. I've been occupied with two major projects.
  1. Nothin But .NET Bootcamp
  2. Whatisluxury.com
In October 2008, I was given the opportunity to go to J.P Boodhoo's Nothin But .NET bootcamp (http://www.jpboodhoo.com/training.oo). It was a truly humbling experience and gave me a chance to set some goals for myself, in terms of self improvement in the .NET field.

At the end of December 2008, I launched an online luxury magazine for the publishers of Beverly Hills Report called WhatIsLuxury? (http://www.whatisluxury.com).

It was a fun experience and in the coming weeks, I would like to share some of the tricks & techniques I've used to accomplish some challenges that their requirements presented, including:
  • AJAX-enabled story rotation on the home page
  • Full Page Ad rotation every X number of pages
  • AJAX-enabled thumbnail-based navigation
Thanks for reading and stay tuned!