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);
}
}
}
}
Comments
Anonymous
December 18, 2008
PingBack from http://stevepietrek.com/2008/12/18/links-12182008/Anonymous
December 23, 2008
Why not just use Document Properties Quick Parts (Word 2007 > Insert > Quick Part) in the header/footer? No code required, and it'll sync data in the document with the list. Ah! Unless you're just Uploading documents, rather than creating them from a template attached to you library?Anonymous
May 14, 2009
SharePoint Create your own customized usage report solution step by step SharePoint WebPart PropertyAnonymous
April 27, 2010
What if versioning is enabled in the library ? I think it will create additional version when you update the stream in ItemAdded event ...Is there a way to suppress it ?Anonymous
June 16, 2010
Hii, i used this solution, it works fine. But sometime i got error "File containrs corrupted data : Window Base " . So unable to add header and footer on document. Do you know why i am getting this error? how can i check file can have corrupted data? What is the solution for this? i got error here : using (Package wdPackage = Package.Open(fileContent, FileMode.Open, FileAccess.ReadWrite))Anonymous
October 22, 2012
thanks for above post,it is very helpful for me. i want to know how we insert custom meta data in header/footer. I tried lot but error comes tht files is already modified by this user.plz give some suggestion