Introduction
With the OpenXML SDK, you can edit docx files without having Microsoft Word installed.
In this particular situation, I'm editing the custom properties of a docx file, which are commonly used to store some application's info for further use, or even for some add-in that we developed as well.
Using the code
This article is really simple; its purpose is to spread the word, just showing you what to do based on MSDN.
For starters, the discovery was when I found the OpenXML SDK that allows me to manipulate Word documents without having Office installed on the server that runs my application, which is a major breakthrough! The code that I use which adds custom properties was taken from MSDN, and I will show it to you here:
The PropertyTypes
enum:
public enum PropertyTypes
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble,
}
The method:
public bool WDSetCustomProperty(string docName, string propertyName,
object propertyValue, PropertyTypes propertyType)
{
const string documentRelationshipType =
"http://schemas.openxmlformats.org/officeDocument/" +
"2006/relationships/officeDocument";
const string customPropertiesRelationshipType =
"http://schemas.openxmlformats.org/officeDocument/" +
"2006/relationships/custom-properties";
const string customPropertiesSchema =
"http://schemas.openxmlformats.org/officeDocument/" +
"2006/custom-properties";
const string customVTypesSchema =
"http://schemas.openxmlformats.org/officeDocument/" +
"2006/docPropsVTypes";
bool retVal = false;
PackagePart documentPart = null;
string propertyTypeName = "vt:lpwstr";
string propertyValueString = null;
switch (propertyType)
{
case PropertyTypes.DateTime:
propertyTypeName = "vt:filetime";
if (propertyValue.GetType() == typeof(System.DateTime))
{
propertyValueString = string.Format("{0:s}Z",
Convert.ToDateTime(propertyValue));
}
break;
case PropertyTypes.NumberInteger:
propertyTypeName = "vt:i4";
if (propertyValue.GetType() == typeof(System.Int32))
{
propertyValueString =
Convert.ToInt32(propertyValue).ToString();
}
break;
case PropertyTypes.NumberDouble:
propertyTypeName = "vt:r8";
if (propertyValue.GetType() == typeof(System.Double))
{
propertyValueString =
Convert.ToDouble(propertyValue).ToString();
}
break;
case PropertyTypes.Text:
propertyTypeName = "vt:lpwstr";
propertyValueString = Convert.ToString(propertyValue);
break;
case PropertyTypes.YesNo:
propertyTypeName = "vt:bool";
if (propertyValue.GetType() == typeof(System.Boolean))
{
propertyValueString =
Convert.ToBoolean(propertyValue).ToString().ToLower();
}
break;
}
if (propertyValueString == null)
{
throw new InvalidDataException("Invalid parameter value.");
}
using (Package wdPackage = Package.Open(
docName, FileMode.Open, FileAccess.ReadWrite))
{
foreach (PackageRelationship relationship in
wdPackage.GetRelationshipsByType(documentRelationshipType))
{
Uri documentUri = PackUriHelper.ResolvePartUri(
new Uri("/", UriKind.Relative), relationship.TargetUri);
documentPart = wdPackage.GetPart(documentUri);
break;
}
PackagePart customPropsPart = null;
foreach (PackageRelationship relationship in
wdPackage.GetRelationshipsByType(
customPropertiesRelationshipType))
{
Uri documentUri = PackUriHelper.ResolvePartUri(
new Uri("/", UriKind.Relative), relationship.TargetUri);
customPropsPart = wdPackage.GetPart(documentUri);
break;
}
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("d", customPropertiesSchema);
nsManager.AddNamespace("vt", customVTypesSchema);
Uri customPropsUri =
new Uri("/docProps/custom.xml", UriKind.Relative);
XmlDocument customPropsDoc = null;
XmlNode rootNode = null;
if (customPropsPart == null)
{
customPropsDoc = new XmlDocument(nt);
customPropsPart = wdPackage.CreatePart(
customPropsUri,
"application/vnd.openxmlformats-officedocument.custom-properties+xml");
rootNode = customPropsDoc.
CreateElement("Properties", customPropertiesSchema);
rootNode.Attributes.Append(
customPropsDoc.CreateAttribute("xmlns:vt"));
rootNode.Attributes["xmlns:vt"].Value = customVTypesSchema;
customPropsDoc.AppendChild(rootNode);
wdPackage.CreateRelationship(customPropsUri,
TargetMode.Internal, customPropertiesRelationshipType);
}
else
{
customPropsDoc = new XmlDocument(nt);
customPropsDoc.Load(customPropsPart.GetStream());
rootNode = customPropsDoc.DocumentElement;
}
string searchString =
string.Format("d:Properties/d:property[@name='{0}']",
propertyName);
XmlNode node = customPropsDoc.SelectSingleNode(
searchString, nsManager);
XmlNode valueNode = null;
if (node != null)
{
if (node.HasChildNodes)
{
valueNode = node.ChildNodes[0];
if (valueNode != null)
{
string typeName = valueNode.Name;
if (propertyTypeName == typeName)
{
valueNode.InnerText = propertyValueString;
retVal = true;
}
else
{
node.ParentNode.RemoveChild(node);
node = null;
}
}
}
}
if (node == null)
{
string pidValue = "2";
XmlNode propertiesNode = customPropsDoc.DocumentElement;
if (propertiesNode.HasChildNodes)
{
XmlNode lastNode = propertiesNode.LastChild;
if (lastNode != null)
{
XmlAttribute pidAttr = lastNode.Attributes["pid"];
if (!(pidAttr == null))
{
pidValue = pidAttr.Value;
int value = 0;
if (int.TryParse(pidValue, out value))
{
pidValue = Convert.ToString(value + 1);
}
}
}
}
node = customPropsDoc.CreateElement("property", customPropertiesSchema);
node.Attributes.Append(customPropsDoc.CreateAttribute("name"));
node.Attributes["name"].Value = propertyName;
node.Attributes.Append(customPropsDoc.CreateAttribute("fmtid"));
node.Attributes["fmtid"].Value = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
node.Attributes.Append(customPropsDoc.CreateAttribute("pid"));
node.Attributes["pid"].Value = pidValue;
valueNode = customPropsDoc.
CreateElement(propertyTypeName, customVTypesSchema);
valueNode.InnerText = propertyValueString;
node.AppendChild(valueNode);
rootNode.AppendChild(node);
retVal = true;
}
customPropsDoc.Save(customPropsPart.GetStream(
FileMode.Create, FileAccess.Write));
}
return retVal;
}
Usage:
WDSetCustomProperty("C:\\demo.docx", "Completed",
false, PropertyTypes.YesNo);
WDSetCustomProperty("C:\\demo.docx", "Completed",
new DateTime(2008, 1, 1), PropertyTypes.DateTime);
The purpose of this code is to write custom properties, for my Word add-in to work properly, which is quite handy in most situations.
Hope this was as much value to you as it was to me! Thank you very much.