SharePoint 2007 (MOSS/WSS) - Adding header and footer in Word Document (.docx) with ItemAdded Event Receiver using OpenXML

I used OpenXML to add the two metadata column values - document name as the header and modified date as footer while someone upload a document in document library. Please include a reference of WindowsBase and DocumentFormat.OpenXml in the project. WindowsBase is included in .Net framework 3.0/3.5 and you have to download DocumentFormat.OpenXml separately.

Here is the code:

using System;

using System.Security.Permissions;

using System.Runtime.InteropServices;

using Microsoft.SharePoint;

using System.IO;

using System.IO.Packaging;

using DocumentFormat.OpenXml.Packaging;

using System.Xml;

using System.Collections.Generic;

namespace AddHeaderFooterReceiver

{

    public class AddHeaderFooterEventReceiver : SPItemEventReceiver

    {

        public string GetFooter()

        {

            string footerVal = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><w:ftr xmlns:ve=\"https://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:r=\"https://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:m=\"https://schemas.openxmlformats.org/officeDocument/2006/math\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:wp=\"https://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" xmlns:w10=\"urn:schemas-microsoft-com:office:word\" xmlns:w=\"https://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:wne=\"https://schemas.microsoft.com/office/word/2006/wordml\"><w:p w:rsidR=\"00C24C70\" w:rsidRDefault=\"00C24C70\"><w:pPr><w:pStyle w:val=\"Footer\" /></w:pPr><w:r><w:t>Hi</w:t></w:r></w:p><w:p w:rsidR=\"00C24C70\" w:rsidRDefault=\"00C24C70\"><w:pPr><w:pStyle w:val=\"Footer\" /></w:pPr></w:p></w:ftr>";

            return footerVal;

        }

        public string GetHeader()

        {

            string headerVal = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><w:hdr xmlns:ve=\"https://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:o=\"urn:schemas-microsoft-com:office:office\" xmlns:r=\"https://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:m=\"https://schemas.openxmlformats.org/officeDocument/2006/math\" xmlns:v=\"urn:schemas-microsoft-com:vml\" xmlns:wp=\"https://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing\" xmlns:w10=\"urn:schemas-microsoft-com:office:word\" xmlns:w=\"https://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:wne=\"https://schemas.microsoft.com/office/word/2006/wordml\"><w:p w:rsidR=\"00C8737A\" w:rsidRDefault=\"00C8737A\"><w:pPr><w:pStyle w:val=\"Header\" /></w:pPr><w:r><w:t>hello</w:t></w:r></w:p><w:p w:rsidR=\"00C8737A\" w:rsidRDefault=\"00C8737A\"><w:pPr><w:pStyle w:val=\"Header\" /> </w:pPr></w:p></w:hdr>";

            return headerVal;

        }

        public void WDAddHeader(Stream headerContent, Stream fileContent)

        {

            // Given a document name, and a stream containing valid header content,

            // add the stream content as a header in the document.

            const string documentRelationshipType = "https://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";

            const string wordmlNamespace = "https://schemas.openxmlformats.org/wordprocessingml/2006/main";

            const string headerContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml";

            const string headerRelationshipType = "https://schemas.openxmlformats.org/officeDocument/2006/relationships/header";

            const string relationshipNamespace = "https://schemas.openxmlformats.org/officeDocument/2006/relationships";

            PackagePart documentPart = null;

            using (Package wdPackage = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite))

            {

                // Get the main document part (document.xml).

                foreach (System.IO.Packaging.PackageRelationship relationship in wdPackage.GetRelationshipsByType(documentRelationshipType))

                {

                    Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);

                    documentPart = wdPackage.GetPart(documentUri);

                    // There is only one officeDocument.

                    break;

                }

                Uri uriHeader = new Uri("/word/header1.xml", UriKind.Relative);

                if (wdPackage.PartExists(uriHeader))

                {

                    // Although you can delete the relationship

                    // to the existing node, the next time you save

       // the document after making changes, Word

                    // will delete the relationship.

                    wdPackage.DeletePart(uriHeader);

                }

                // Create the header part.

                PackagePart headerPart = wdPackage.CreatePart(uriHeader, headerContentType);

                // Load the content from the input stream.

                // This may seem redundant, but you must read it at some point.

                // If you ever need to analyze the contents of the header,

                // at least it is already in an XmlDocument.

                // This code uses the XmlDocument object only as

                // a "pass-through" -- giving it a place to hold as

                // it moves from the input stream to the output stream.

                // The code could read each byte from the input stream, and

                // write each byte to the output stream, but this seems

                // simpler...

                XmlDocument headerDoc = new XmlDocument();

                headerContent.Position = 0;

                headerDoc.Load(headerContent);

                // Write the header out to its part.

                headerDoc.Save(headerPart.GetStream());

                // Create the document's relationship to the new part.

                PackageRelationship rel = documentPart.CreateRelationship(uriHeader, TargetMode.Internal, headerRelationshipType);

                string relID = rel.Id;

                // Manage namespaces to perform Xml XPath queries.

                NameTable nt = new NameTable();

                XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);

                nsManager.AddNamespace("w", wordmlNamespace);

                // Get the document part from the package.

                // Load the XML in the part into an XmlDocument instance.

                XmlDocument xdoc = new XmlDocument(nt);

                xdoc.Load(documentPart.GetStream());

                // Find the node containing the document layout.

                XmlNode targetNode = xdoc.SelectSingleNode("//w:sectPr", nsManager);

                if (targetNode != null)

                {

                    // Delete any existing references to headers.

                    XmlNodeList headerNodes = targetNode.SelectNodes("./w:headerReference", nsManager);

                    foreach (System.Xml.XmlNode headerNode in headerNodes)

                    {

                        targetNode.RemoveChild(headerNode);

                    }

                    // Create the new header reference node.

                    XmlElement node = xdoc.CreateElement("w:headerReference", wordmlNamespace);

                    XmlAttribute attr = node.Attributes.Append(xdoc.CreateAttribute("r:id", relationshipNamespace));

                    attr.Value = relID;

                    node.Attributes.Append(attr);

                    targetNode.InsertBefore(node, targetNode.FirstChild);

                }

                // Save the document XML back to its part.

                xdoc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write));

            }

        }

        public void WDAddFooter(Stream footerContent, Stream fileContent)

        {

            // Given a document name, and a stream containing valid footer content,

            // add the stream content as a footer in the document.

            const string documentRelationshipType = "https://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";

            const string wordmlNamespace = "https://schemas.openxmlformats.org/wordprocessingml/2006/main";

            const string footerContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml";

            const string footerRelationshipType = "https://schemas.openxmlformats.org/officeDocument/2006/relationships/footer";

            const string relationshipNamespace = "https://schemas.openxmlformats.org/officeDocument/2006/relationships";

       PackagePart documentPart = null;

            using (Package wdPackage = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite))

            {

                // Get the main document part (document.xml).

                foreach (System.IO.Packaging.PackageRelationship relationship in wdPackage.GetRelationshipsByType(documentRelationshipType))

                {

                    Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);

          documentPart = wdPackage.GetPart(documentUri);

                    // There is only one officeDocument.

                    break;

                }

                Uri uriFooter = new Uri("/word/footer1.xml", UriKind.Relative);

                if (wdPackage.PartExists(uriFooter))

                {

                    // Although you can delete the relationship

                    // to the existing node, the next time you save

                    // the document after making changes, Word

                    // will delete the relationship.

                    wdPackage.DeletePart(uriFooter);

                }

                // Create the footer part.

                PackagePart footerPart = wdPackage.CreatePart(uriFooter, footerContentType);

                // Load the content from the input stream.

                // This may seem redundant, but you must read it at some point.

                // If you ever need to analyze the contents of the footer,

                // at least it is already in an XmlDocument.

                // This code uses the XmlDocument object only as

                // a "pass-through" -- giving it a place to hold as

                // it moves from the input stream to the output stream.

                // The code could read each byte from the input stream, and

                // write each byte to the output stream, but this seems

                // simpler...

                XmlDocument footerDoc = new XmlDocument();

                footerContent.Position = 0;

                footerDoc.Load(footerContent);

                // Write the footer out to its part.

                footerDoc.Save(footerPart.GetStream());

                // Create the document's relationship to the new part.

                PackageRelationship rel = documentPart.CreateRelationship(uriFooter, TargetMode.Internal, footerRelationshipType);

                string relID = rel.Id;

                // Manage namespaces to perform Xml XPath queries.

                NameTable nt = new NameTable();

                XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);

                nsManager.AddNamespace("w", wordmlNamespace);

                // Get the document part from the package.

                // Load the XML in the part into an XmlDocument instance.

                XmlDocument xdoc = new XmlDocument(nt);

                xdoc.Load(documentPart.GetStream());

                // Find the node containing the document layout.

                XmlNode targetNode = xdoc.SelectSingleNode("//w:sectPr", nsManager);

                if (targetNode != null)

                {

                    // Delete any existing references to footers.

       XmlNodeList footerNodes = targetNode.SelectNodes("./w:footerReference", nsManager);

                    foreach (System.Xml.XmlNode footerNode in footerNodes)

                    {

                        targetNode.RemoveChild(footerNode);

  }

                    // Create the new footer reference node.

                    XmlElement node = xdoc.CreateElement("w:footerReference", wordmlNamespace);

                    XmlAttribute attr = node.Attributes.Append(xdoc.CreateAttribute("r:id", relationshipNamespace));

                    attr.Value = relID;

                    node.Attributes.Append(attr);

                    targetNode.InsertBefore(node, targetNode.FirstChild);

                }

                // Save the document XML back to its part.

                xdoc.Save(documentPart.GetStream(FileMode.Create, FileAccess.Write));

            }

        }

        public override void ItemAdded(SPItemEventProperties properties)

        {

            string extension = properties.ListItem.Url.Substring(properties.ListItem.Url.LastIndexOf(".") + 1);

            if (extension == "docx")

            {

                string headerContent = GetHeader().Replace("hello", properties.ListItem["Name"].ToString());

                string footerContent = GetFooter().Replace("Hi", properties.ListItem["Modified"].ToString());

                Stream headerStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(headerContent));

      Stream footerStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(footerContent));

                MemoryStream fileStream = new MemoryStream();

                fileStream.Write(properties.ListItem.File.OpenBinary(), 0, (int)properties.ListItem.File.TotalLength);

                WDAddHeader(headerStream, fileStream);

                WDAddFooter(footerStream, fileStream);

                properties.ListItem.File.SaveBinary(fileStream);

            }

        }

    }

}