Click here to Skip to main content
15,881,204 members
Articles / Productivity Apps and Services / Microsoft Office

Manipulate Docx with C# without Microsoft Word installed with OpenXML SDK

Rate me:
Please Sign up or sign in to vote.
4.69/5 (9 votes)
15 Jun 2010CPOL 94.2K   52   13
Use C# and the OpenXML SDK to manipulate docx without MSO.

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:

C#
public enum PropertyTypes
{
  YesNo,
  Text,
  DateTime,
  NumberInteger,
  NumberDouble,
}

The method:

C#
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;

    //  Calculate the correct type.
    switch (propertyType)
    {
        case PropertyTypes.DateTime:
          propertyTypeName = "vt:filetime";
          //  Make sure 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.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))
          {
            //  Must be lower case!
            propertyValueString =
              Convert.ToBoolean(propertyValue).ToString().ToLower();
          }
          break;
    }

    if (propertyValueString == null)
    {
        //  If the code cannot convert the 
        //  property to a valid value, throw an exception.
        throw new InvalidDataException("Invalid parameter value.");
    }

    using (Package wdPackage = Package.Open(
           docName, FileMode.Open, FileAccess.ReadWrite))
    {
        //  Get the main document part (document.xml).
        foreach (PackageRelationship relationship in
        wdPackage.GetRelationshipsByType(documentRelationshipType))
        {
            Uri documentUri = PackUriHelper.ResolvePartUri(
                new Uri("/", UriKind.Relative), relationship.TargetUri);
            documentPart = wdPackage.GetPart(documentUri);
            //  There is only one document.
            break;
        }
        
        //  Work with the custom properties part.
        PackagePart customPropsPart = null;

        //  Get the custom part (custom.xml). It may not exist.
        foreach (PackageRelationship relationship in
          wdPackage.GetRelationshipsByType(
          customPropertiesRelationshipType))
        {
            Uri documentUri = PackUriHelper.ResolvePartUri(
                new Uri("/", UriKind.Relative), relationship.TargetUri);
            customPropsPart = wdPackage.GetPart(documentUri);
            //  There is only one custom properties part, 
            // if it exists at all.
            break;
        }

        //  Manage namespaces to perform Xml XPath queries.
        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);

          //  The part does not exist. Create it now.
          customPropsPart = wdPackage.CreatePart(
            customPropsUri, 
            "application/vnd.openxmlformats-officedocument.custom-properties+xml");

          //  Set up the rudimentary custom part.
          rootNode = customPropsDoc.
            CreateElement("Properties", customPropertiesSchema);
          rootNode.Attributes.Append(
            customPropsDoc.CreateAttribute("xmlns:vt"));
          rootNode.Attributes["xmlns:vt"].Value = customVTypesSchema;

          customPropsDoc.AppendChild(rootNode);

          //  Create the document's relationship to the 
          //  new custom properties part.
          wdPackage.CreateRelationship(customPropsUri,
            TargetMode.Internal, customPropertiesRelationshipType);
        }
        else
        {
          //  Load the contents of the custom properties part 
          //  into an XML document.
          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)
        {
            //  You found the node. Now check its type.
            if (node.HasChildNodes)
            {
                valueNode = node.ChildNodes[0];
                if (valueNode != null)
                    {
                    string typeName = valueNode.Name;
                    if (propertyTypeName == typeName)
                    {
                        //  The types are the same. 
                        //  Replace the value of the node.
                        valueNode.InnerText = propertyValueString;
                        //  If the property existed, and its type
                        //  has not changed, you are finished.
                        retVal = true;
                    }
                    else
                    {
                        //  Types are different. Delete the node
                        //  and clear the node variable.
                        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;
                        //  Increment pidValue, so that the new property
                        //  gets a pid value one higher. This value should be 
                        //  numeric, but it never hurt so to confirm.
                        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;
        }

        //  Save the properties XML back to its part.
        customPropsDoc.Save(customPropsPart.GetStream(
                            FileMode.Create, FileAccess.Write));

    }
    return retVal;
}

Usage:

C#
// Change an existing property's value or create a new one with the supplied value
WDSetCustomProperty("C:\\demo.docx", "Completed", 
  false, PropertyTypes.YesNo);

// Change an existing property's value or create a new one with the supplied value
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Truphone
Portugal Portugal
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionEasy to use alternative Pin
Robert Hutch16-Feb-12 3:09
Robert Hutch16-Feb-12 3:09 
AnswerRe: Easy to use alternative Pin
ricmrodrigues17-Feb-12 0:07
ricmrodrigues17-Feb-12 0:07 
GeneralRe: Easy to use alternative Pin
Robert Hutch17-Feb-12 4:35
Robert Hutch17-Feb-12 4:35 
GeneralRe: Easy to use alternative Pin
ricmrodrigues17-Feb-12 5:01
ricmrodrigues17-Feb-12 5:01 
GeneralRe: Easy to use alternative Pin
Robert Hutch19-Feb-12 22:42
Robert Hutch19-Feb-12 22:42 
GeneralRe: Easy to use alternative Pin
atulonweb@gmail.com23-Dec-15 20:44
atulonweb@gmail.com23-Dec-15 20:44 
GeneralfleXdoc Pin
hmmhazeb15-Jun-10 10:41
hmmhazeb15-Jun-10 10:41 
Generalcode dump Pin
Jaime Olivares14-Jun-10 16:35
Jaime Olivares14-Jun-10 16:35 
Ric,
This looks like a code dump. You don't need to put all this long code into the article's body. A class packaging it and a code snippet explaining usage will be of great help.
Best regards,
Jaime.

GeneralRe: code dump Pin
ricmrodrigues14-Jun-10 22:08
ricmrodrigues14-Jun-10 22:08 
GeneralRe: code dump Pin
deBaires15-Jun-10 3:12
deBaires15-Jun-10 3:12 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:18
ricmrodrigues15-Jun-10 23:18 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:33
ricmrodrigues15-Jun-10 23:33 
GeneralRe: code dump Pin
ricmrodrigues15-Jun-10 23:33
ricmrodrigues15-Jun-10 23:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.