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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.