Featured Post

SQL Query in SharePoint

The "FullTextSqlQuery" object's constructor requires an object that has context but don't be fooled. This context will no...

Showing posts with label openxml. Show all posts
Showing posts with label openxml. Show all posts

Friday, December 6, 2013

Understanding word numbering manipulation

I've created a document with two types of numbering references. The first references a style which is linked to a number and the second is linked a style but has manual numbering assigned. I've added an image so you can see what this looks like in word. Note that to the front end user it looks like the only difference is the style.

However in the xml there is a slight difference. Here is an section of document.xml showing the xml for the first set of numbering. Note that the first two lines do not have any reference to a w:numId but the second two lines do. This is because the style "DemoStyle" has a reference to w:numId but only the first level. When I indented lines 3 & 4 they needed to be assigned a new numbering level/w:ilvl. Note that these paragraphs are linked to numId "1"
If you take a look at the styles.xml you will see that DemoStyle is also linked to numId "1"
Going onto the manually applied numbering see the xml below. You can see that the numbering has been assigned to all four paragraphs, not just the indented lines.
In the numbering.xml document you will find these numbering items as w:num nodes. These nodes allow multiple items to reference the same abstract number (not shown in this case but is possible).

The w:abstractNumId then references the w:abstractNum. This contains all the numbering information.
To access the numbering part in a document via OpenXml you can use the following code:

byte[] document = File.ReadAllBytes("C:\Demo Doc.docx");
using (MemoryStream memoryStream = new MemoryStream())
{
  memoryStream.Write(document, 0, document.Length);
  using (WordprocessingDocument wordprocessingDocument =     WordprocessingDocument.Open(memoryStream, true))
  {
     NumberingPart numberingPart  = wordprocessingDocument.MainDocumentPart.NumberingPart;
  }
}

Thursday, December 5, 2013

Understanding word style manipulation

To edit your document's styles you can click on the bottom right hand corner of the styles section on the home page.
When manipulating styles the most important thing to note are where styles are referenced. All styles are referenced by id so if you want to change the properties of a style this is quite a simple change and can be done on the specific style node applicable.
So the most obvious place this style could be referenced is inside the document and any headers/footers as you can see in the image below I have a paragraph with the text "Coding Recipes" that is linked to "Demo Style"

There are two other places this style could be referenced. If you look at the style xml is has a "w:basedOn" node pointing to the "Normal" style. It is possible to base a style on any other style so this is another place it could be referenced. The last place is inside the numbering but that is only if your style is linked to a custom number.

By adding a number to my style you can see that the style xml now has some numbering information.
If you used a built in number for your style than you wouldn't have to worry about any other references however if your style was linked to a custom number then a numbering element would get created in numbering.xml and this would have a reference to your style as well as shown in the image below
I'll explain more about numbering in my next post but if you are wondering how the two are referenced you can determine this by finding the w:num element in numbering.xml that has the id "1" (referenced in the w:numid node of the style). You'll then see that numid "1" is linked to abstractNumId "0" which you can see in the image above.



Understanding word headers, footers and section break manipulation

To access a header or footer you can double click at the top or bottom of your word document page.


I'm going to go through some of the areas of the headers/footers. Under the options section there are 2 check boxes "Different First Page" and "Different Odd & Even Pages". So if you check these boxes word will allow the first page to have a different header & footer. The odd and even check box will allow every second page to have a different header/footer. So how does this look in the xml?
In the xml you can see 3 headerReferences and 3 footReferences and they are a child of the "w:sectPr"/section properties node.  A document must contain at least one section property at the bottom of the body and it can contain more depending on whether you add section breaks inside the document. There are 3 types of header/footer references that can be associated with one section property - "even", "default" and "first".

To access the header/footer parts in a document using open xml you can use the following code

byte[] document = File.ReadAllBytes("C:\Demo Doc.docx");
using (MemoryStream memoryStream = new MemoryStream())
{
  memoryStream.Write(document, 0, document.Length);
  using (WordprocessingDocument wordprocessingDocument =     WordprocessingDocument.Open(memoryStream, true))
  {
     foreach (HeaderPart headerPart in wordprocessingDocument.MainDocumentPart.HeaderParts)
     {
         // do something
     }

     foreach (FooterPart footerPart in wordprocessingDocument.MainDocumentPart.FooterParts)
     {
        // do something
     }
  }
}

Another way to have different headers/footers on a page is to insert a section break in your document. To do this go to Page Layout -> Breaks -> Next Page.

Go into the header/footer and then unselect the "Link to Previous" option. Then modify the header/footer.

Something important to note about section breaks if you are planning on modifying them programmatically is the placement of the section break in document.xml. As I mentioned earlier, the document.xml will always have a section break at the end of the body element. The elements that are applicable to this section are all the ones above it - until the previous section break (if there is one). But all the other section breaks refer to the elements below them - until the next section break.

For example. I've created a new word document and inserted 2 section breaks. On each page I have added text to indicate what page number it is. Look at the xml that got generated to understand further what I am trying to explain:

What is also very important to note here is that the last section property exists as a child of body however the other section properties are nested inside a paragraph object (w:p).

Tuesday, December 3, 2013

Understanding basic office document manipulation

This post for anyone who wants to know on a very base level how office documents work regarding their xml structure.

So the first thing you should know is that all office documents are actually a collection of xml files. The names of the files and folders inside this collection can be different depending on what type of office document you are working with (word, powerpoint, excel, etc). However in this tutorial we are going to be working with a word document.

Opening a very basic word document using a decompression tool (I used WinRAR in this example) you will see the following contents.

the _rels folder contains all the relationship files. These relationships are used to map xml files together.




So if you go into the word folder you will see a few different xml files.

If you go inside the _rels folder you will find a corresponding xml file to document.xml called document.xml.rels.






I added a chart to my document and you can see some new folders have been added. Opening document.xml you will see the following xml structure.

The main node is "document" and directly underneath this you can find the "body". This is the standard structure for this file. The body will mainly contain paragraphs (w:p) however there will be a few other nodes as you can see at the bottom there is a "w:sectPr" node. This is a section property node which contains information about the page (size, margin, columns, header, footer, etc). This node will always be found at the bottom of the body node. If you insert a section break inside your document then you will find other nodes like this inside the body node.

In this example I have inserted a chart. What this has done is inserted a w:drawing element which contains information about the chart. The data for the actual chart image however is stored elsewhere. To reference this data there is a r:id node on the c:chart element with a value of "rId5". If I then open the document.xml.rels file I can then see this id then points to the file charts/chart1.xml
So when you open this file in word it will deserialize these xml files into COM objects and show the document. If the xml markup does not correspond to the objects then you will get a corruption error in word. For example if I delete the charts folder and try to open the file I will get the following error:
You could also modify the contents of the xml file manually by extracting it from the docx modifing the contents and dragging it back into WinRAR. This is quite handy when you are trying to troubleshoot.

Monday, March 7, 2011

Issues with AltChunk

I managed to find the underlying problem to a rather annoying bug using AltChunk. I've generated thousands of reports using OpenXml but this one word document would always break xml markup of the document when inserting it using AltChunk.

After stripping the document to only contain the parts that broke it I managed to find out that the "DocumentSettingsPart" contained some elements called SmartTagType. I've never seen these before and not sure what they are used for but the moment I removed them from the document my AltChunk insertion started to work so I now remove them from all my documents before I insert using AltChunk.

I wonder if this is a known bug - will ask on the forum.

Here's some code:

// Get a list of smart tags in the document settings part and remove these
List<SmartTagType> smartTags = mainDocumentPart.DocumentSettingsPart.Settings.Descendants<SmartTagType>();

// Loop backwards otherwise the elements orders change
for (int i = smartTags.Count - 1; i >= 0; i--)
{
  smartTags[i].Remove();
}

Friday, March 4, 2011

Maintain original image size when moving images in open xml

When inserting images into content control boxes using openxml, the image dimensions will not change to those of the image, you will need to manually do this.

Which objects?

There are two places where the image size needs to be set, inside the DocumentFormat.OpenXml.Drawing.Pictures.Picture object and the DocumentFormat.OpenXml.Drawing.Wordprocessing.Inline object. Both of these objects contain an DocumentFormat.OpenXml.Drawing.Wordprocessing.Extent object which contains the Cx and Cy values for the image.

What size?

Now the next problem is knowing what the Cx and Cy values should be set to. Use the System.Drawing.Image object to get the Width and Height properties from your image. However, word image dimensions are not stored in pixels, they are stored in points.

Converting pixels to points can be very complicated when you're working with text but luckily with images there is a constant value we can use to calculate the points from a pixel value, which is 9525.

Code example please?

Wednesday, February 16, 2011

Trouble with OpenXML and MemoryStreams?

I've been struggling for a while with changing my project to use Memory Streams instead of physical files. There were a few things I was doing wrong which you probably wouldn't pick up if you started off using physical files but because I was now refactoring I was missing a few pointers.

A) MainDocumentPart.Document.Save()

When working with the physical file, there is no need to save but when working with a memory stream you need to call this method just before you close your word document.

B) MemoryStream.WriteTo(fileStream)

Because I was working from a document that already existed I would open the document and not see any changes. This was because I was not saving the document back to the file. Very rookie, I know, but it happened and maybe this helps someone out there. More importantly see next point:

C) When to save your memory stream back to the file!!!!

I was writing my memory stream back to the file while the file was still open. This did not throw any exceptions and it still does not make sense to me why this is a problem but it is! Make sure that you always save the memory stream to a file after you have closed the word document but when your memory stream is still open. This caused me hours of pain :( If anybody knows why this happens please let me know.

Here is an example of what your code should look like

Tuesday, January 25, 2011

Moving images in word headers using OpenXML

From what I discovered moving images from one header in a document to another seems to be a huge problem for most people so I decided to try do this myself. I'm not 100% sure what other issues it might cause but I when I run the "Validate" command in the OpenXML Productivity Tools I get no errors and the images I'm moving are displaying correctly so for now I'm giving it the go ahead :)


Background


To move data from one document to another you can use altChunk but this does not copy headers and footers. If this doesn't phase you - you can read more about altChunk on Brian Jones blog: entry: http://blogs.msdn.com/b/brian_jones/archive/2008/12/08/the-easy-way-to-assemble-multiple-word-documents.aspx.


Brian Jones has tons of examples of how to manipulate documents. He also has some nice information about some open source code called "DocumentBuilder" which I stumbled upon but when I run the "Validate" command using the OpenXML Productivity Tools I get an error for each document I tried to merge so I decided this was not a good option for me. If you are interested though check it out here:  http://www.pubsub.com/events/5e5f8d3fd2346a54739b8b6bb0438b82


Another way to move elements from one part of a document to another is to actually use the "FeedData" method and pass in the other element by getting its Stream. This however doesn't work when moving images in headers between documents.

Looking Deeper




Create a blank document and insert any image into it's header. Use the OpenXML Productivity Tool to open the document and expand it's /word/document.xml node. You will see that there are few header xml nodes inside the document xml node. Thats because there are 3 types of headers inside a document or none at all. The three types distuinguish between First, Default and Even.


If you go to expand "w:document", "w:body" you will see "w:sectPr". If you reflect this node you will see the references to your headers. In my document my "Default" header has been referenced using id "rId8". If you reflect your "/word/document.xml node you should see which Header has been assigned "rId8". This helped me to find out which HeaderPart I need to retrieve to find the elements I'm looking for.


Start Coding


A HeaderPart can contain a few things but if I just have a plain header with an image it should contain a "/word/media/image1.png" element as well as a "w:hdr" element. What I did was create a new Header by passing the old headers OuterXML into the constructor and manually copy paste the Images seperately and this worked. Here is some example code:


reportImagePart.FeedData(headerImagePart.GetStream());

private static void ReplaceImages(HeaderPart headerPart, HeaderPart reportHeaderPart) 

{ 
  foreach (IdPartPair partPair in headerPart.Parts)   
  {
    OpenXmlPart openXmlPart = partPair.OpenXmlPart;   
    Type type = openXmlPart.GetType();   
    if (type.Name == "ImagePart")     
    {
      ImagePart headerImagePart = (ImagePart)openXmlPart;     
      string idImage = headerPart.GetIdOfPart(openXmlPart);     
      ImagePart reportImagePart = reportPart.AddNewPart<ImagePart>("image/png", idImage);
      Header headerNew = new Header(headerPart.Header.OuterXml);
    }
  }
}

This could probably be coded better but I'm still in the POC process. If anyone is having any issues with this strategy please let me know!!!