Export (0) Print
Expand All
Expand Minimize
2 out of 7 rated this helpful - Rate this topic

Setting Custom Properties in Word 2010 Documents by Using the Open XML SDK 2.0

Office 2010

Office Visual How To

Summary:  Use strongly typed classes in the Open XML SDK 2.0 to modify custom document properties in a Word 2007 or Word 2010 document, without loading the document into Microsoft Word.

Applies to:  Microsoft Office 2010 | Visual Studio 2010 | 2007 Microsoft Office system

Published:  August 2010

Provided by:  Ken Getz, MVP, MCW Technologies, LLC

Overview

The Open XML file formats enable you to modify custom document properties in a Word 2007 or Word 2010 document. The Open XML SDK 2.0 adds strongly typed classes to simplify access to the Open XML file formats. The SDK is designed to simplify the task of modifying custom document properties, and the code sample that is included with this Visual How To shows how to use the SDK to do this.

The sample code included with this Visual How To creates and modifies custom document properties in a Word 2007 or Word 2010 document. To use the sample code, install the Open XML SDK 2.0 from the link listed in the Explore It section. The sample code is included as part of a set of code examples for the Open XML SDK 2.0. The Explore It section also includes a link to the full set of code examples, although you can use the sample code without downloading and installing the code examples.

The sample application modifies custom properties in a document that you supply, calling the WDSetCustomProperty method in the sample to do the work. The method enables you to set a custom property, and returns the current value of the property, if it exists. The calls to the method resemble the following code example.

const string fileName = "C:\\temp\\test.docx";

Console.WriteLine("Manager = " + 
  WDSetCustomProperty(fileName, "Manager", "Peter", 
    PropertyTypes.Text));
Console.WriteLine("Manager = " + 
  WDSetCustomProperty(fileName, "Manager", "Mary", 
    PropertyTypes.Text));
Console.WriteLine("ReviewDate = " + 
  WDSetCustomProperty(fileName, "ReviewDate", 
  DateTime.Parse("12/21/2010"), PropertyTypes.DateTime));

The sample code also includes an enumeration that defines the various possible types of custom properties; The WDSetCustomProperty procedure requires you to supply one of these values when you pass it a property and value.

public enum PropertyTypes : int
{
  YesNo,
  Text,
  DateTime,
  NumberInteger,
  NumberDouble
}

It is important to understand how custom properties are stored in a Word document. The Open XML SDK 2.0 includes, in its tool directory, a useful application named OpenXmlSdkTool.exe, shown in Figure 1. This tool enables you to open a document and view its parts and the hierarchy of parts. Figure 1 shows the test document after you run the code in this sample, and in the right-hand panes, the tool displays both the XML for the part and reflected C# code that you can use to generate the contents of the part.

Figure 1 shows the Open XML SDK 2.0 Productivity Tool that enables you to view the Open XML content of a document.

Figure 1. Open XML SDK 2.0 Productivity Tool

Open XML SDK 2.0 Productivity Tool

If you examine the XML content in Figure 1, you will find information, similar to the following, about the code:

  • Each property in the XML content consists of an XML element, including the name and the value of the property.

  • For each property, the XML content includes an fmtid attribute, always set to the same string value: {D5CDD505-2E9C-101B-9397-08002B2CF9AE}.

  • Each property in the XML content includes a pid attribute, which must include an integer starting at 2 for the first property and incrementing for each successive property.

  • Each property tracks its type (in the figure, the vt:lpwstr and vt:filetime element names define the types for each property).

Code It

The sample code provided with this Visual How To includes the code that is required to create or modify a custom document property in a Word 2007 or Word 2010 document.

Setting Up References

To use the code from the Open XML SDK 2.0, you must add several references to your project. The sample project includes these references, but in your own code, you would must explicitly reference the following assemblies:

  • WindowsBase─This reference may be set for you, depending on the kind of project that you create.

  • DocumentFormat.OpenXml─Installed by the Open XML SDK 2.0.

You should also add the following using/Imports statements to the top of your code file.

using System.IO;
using DocumentFormat.OpenXml.CustomProperties;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.VariantTypes;

Examining the Procedure

The WDSetCustomProperty procedure accepts four parameters:

  • The name of the document to modify (string).

  • The name of the property to add or modify (string)

  • The value of the property (object)

  • The type of property (one of the values in the PropertyTypes enumeration)

public static string WDSetCustomProperty(string fileName, 
  string propertyName, object propertyValue, 
  PropertyTypes propertyType)

The procedure returns the existing value of the property, if it exists. To call the procedure, pass all the parameter values, as shown in the following code example.

const string fileName = "C:\\temp\\test.docx";

Console.WriteLine("Manager = " + 
  WDSetCustomProperty(fileName, "Manager", "Peter", 
    PropertyTypes.Text));

Handling Procedure Parameters

The WDSetCustomProperty procedure starts by setting up some internal variables. Next, it examines the information about the property, and creates a new CustomDocumentProperty based on the parameters that you have specified. The code also maintains a variable named propSet to indicate whether it successfully created the new property object. This code verifies the type of the property value, and then converts the input to the correct type, setting the appropriate property of the CustomDocumentProperty object.

noteNote:

The CustomDocumentProperty type works much like a VBA Variant type. It maintains separate placeholders as properties for the various types of data it might contain.

string returnValue = null;

var newProp = new CustomDocumentProperty();
bool propSet = false;

// Calculate the correct type:
switch (propertyType)
{
  case PropertyTypes.DateTime:
    // Verify that you were passed a real date, 
    // and if so, format correctly. 
    // The date/time value passed in should 
    // represent a UTC date/time.
    if ((propertyValue) is DateTime)
    {
      newProp.VTFileTime = new VTFileTime(string.Format(
        "{0:s}Z", Convert.ToDateTime(propertyValue)));
      propSet = true;
    }

    break;
  case PropertyTypes.NumberInteger:
    if ((propertyValue) is int)
    {
      newProp.VTInt32 = new VTInt32(propertyValue.ToString());
      propSet = true;
    }

    break;
  case PropertyTypes.NumberDouble:
    if (propertyValue is double)
    {
      newProp.VTFloat = new VTFloat(propertyValue.ToString());
      propSet = true;
    }

    break;
  case PropertyTypes.Text:
    newProp.VTLPWSTR = new VTLPWSTR(propertyValue.ToString());
    propSet = true;

    break;
  case PropertyTypes.YesNo:
    if (propertyValue is bool)
    {
      // Must be lowercase.
      newProp.VTBool = new VTBool(
        Convert.ToBoolean(propertyValue).ToString().ToLower());
      propSet = true;
    }
    break;
}
if (!propSet)
{
  // If the code could not convert the 
  // property to a valid value, throw an exception:
  throw new InvalidDataException("propertyValue");
}

At this point, if the code has not thrown an exception, you can assume that the property is valid, and the code sets the FormatId and Name properties of the new custom property.

newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;

Working with the Document

Given the CustomDocumentProperty object, the code next interacts with the document that you supplied in the parameters to the WDSetCustomProperty procedure. The code starts by opening the Word document in read-write mode, using the Open method of the WordProcessingDocument class. The code attempts to retrieve a reference to the custom file properties part, using the CustomFilePropertiesPart property of the document.

using (var document = WordprocessingDocument.Open(fileName, true))
{
  var customProps = document.CustomFilePropertiesPart;
  // Code removed here…
}

If the code cannot find a custom properties part, it creates a new part, and adds a new set of properties to the part.

if (customProps == null)
{
  // No custom properties? Add the part, and the
  // collection of properties now.
  customProps = document.AddCustomFilePropertiesPart();
  customProps.Properties = 
    new DocumentFormat.OpenXml.CustomProperties.Properties();
}

Next, the code retrieves a reference to the custom properties part's Properties property (that is, a reference to the properties themselves). If the code had to create a new custom properties part, you know that this reference is not null, but for existing custom properties parts, it is possible, although highly unlikely, that the Properties property will be null. If so, the code cannot continue.

var props = customProps.Properties;
if (props != null)
{
  // Code removed here…
}

The next step is difficult to justify. If the property already exists, the code retrieves its current value, and then deletes it. Why delete the property? If the new type for the property matches the existing type for the property, the code could set the value of the property to the new value. On the other hand, if the new type does not match, the code must create a new element, deleting the old one (it is the name of the element that defines its type─for more information, see Figure 1). It can be simpler to always delete and recreate the element. The code uses a simple LINQ query to find the first match for the property name.

var prop = props.
  Where(p => ((CustomDocumentProperty)p).
    Name.Value == propertyName).FirstOrDefault();
// Does the property exist? If so, get the return value, 
// and then delete the property.
if (prop != null)
{
  returnValue = prop.InnerText;
  prop.Remove();
}

Now, you will know for sure that the custom property part exists, a property that has the same name as the new property does not exist, and that there may be other existing custom properties. The code performs the following steps:

  1. Appends the new property as a child of the properties collection.

  2. Loops through all the existing properties, and sets the pid attribute to increasing values, starting at 2.

  3. Saves the part.

props.AppendChild(newProp);
int pid = 2;
foreach (CustomDocumentProperty item in props)
{
  item.PropertyId = pid++;
}
props.Save();

Finally, the code returns the stored original property value.

return returnValue;

Provide a test document, and run the sample code. Load the modified document in Word 2007 or Word 2010, and view the custom document properties (you can alternatively load the document into the Open XML SDK Productivity Tool and view the part─verify that the results match those shown in Figure 1.

Sample Procedure

The sample procedure includes the following code.

public static string WDSetCustomProperty(
  string fileName, string propertyName, 
  object propertyValue, PropertyTypes propertyType)
  {

    string returnValue = null;

    var newProp = new CustomDocumentProperty();
    bool propSet = false;

    // Calculate the correct type:
    switch (propertyType)
    {
      case PropertyTypes.DateTime:
        // Verify that you were passed a real date, 
        // and if so, format in the correct way. 
        // The date/time value passed in should 
        // represent a UTC date/time.
        if ((propertyValue) is DateTime)
        {
          newProp.VTFileTime = new VTFileTime(string.Format(
            "{0:s}Z", Convert.ToDateTime(propertyValue)));
          propSet = true;
        }

        break;
      case PropertyTypes.NumberInteger:
        if ((propertyValue) is int)
        {
          newProp.VTInt32 = new VTInt32(propertyValue.ToString());
          propSet = true;
        }

        break;
      case PropertyTypes.NumberDouble:
        if (propertyValue is double)
        {
          newProp.VTFloat = new VTFloat(propertyValue.ToString());
          propSet = true;
        }

        break;
      case PropertyTypes.Text:
        newProp.VTLPWSTR = new VTLPWSTR(propertyValue.ToString());
        propSet = true;

        break;
      case PropertyTypes.YesNo:
        if (propertyValue is bool)
        {
          // Must be lowercase.
          newProp.VTBool = new VTBool(
            Convert.ToBoolean(propertyValue).ToString().ToLower());
          propSet = true;
        }
        break;
    }

    if (!propSet)
    {
      // If the code could not convert the 
      // property to a valid value, throw an exception:
      throw new InvalidDataException("propertyValue");
    }

    // Now that you have handled the parameters,
    // work on the document.
    newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
    newProp.Name = propertyName;

    using (var document = WordprocessingDocument.Open(fileName, true))
    {
      var customProps = document.CustomFilePropertiesPart;
      if (customProps == null)
      {
        // No custom properties? Add the part, and the
        // collection of properties now.
        customProps = document.AddCustomFilePropertiesPart();
        customProps.Properties = new DocumentFormat.OpenXml.
          CustomProperties.Properties();
      }

      var props = customProps.Properties;
      if (props != null)
      {
        var prop = props.
          Where(p => ((CustomDocumentProperty)p).
            Name.Value == propertyName).FirstOrDefault();
        // Does the property exist? If so, get the return value, 
        // and then delete the property.
        if (prop != null)
        {
          returnValue = prop.InnerText;
          prop.Remove();
        }

        // Append the new property, and 
        // fix all the property ID values. 
        // The PropertyId value must start at 2.
        props.AppendChild(newProp);
        int pid = 2;
        foreach (CustomDocumentProperty item in props)
        {
          item.PropertyId = pid++;
        }
        props.Save();
      }
    }
    return returnValue;
  }
}
Read It

The code example in this Visual How To includes many of the issues that you will encounter when you work with the Open XML SDK 2.0. Each example is slightly different. However, the basic concepts are the same. Unless you understand the structure of the part you are trying to work with, even the Open XML SDK 2.0 will not make it possible to interact with the part. Take the time to investigate the objects that you are working with before you start to write code─you will save time.

See It

Watch the video

Watch video

Length: 00:11:25

Click to grab code

Grab the Code

Explore It

About the Author
Ken Getz is a senior consultant with MCW Technologies. He is coauthor of ASP.NET Developers Jumpstart (Addison-Wesley, 2002), Access Developer's Handbook (Sybex, 2001), and VBA Developer's Handbook, 2nd Edition (Sybex, 2001).

Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback

Community Additions

ADD
Show:
© 2014 Microsoft. All rights reserved.