65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 OpenXML SDK 在未安装 Microsoft Word 的情况下用 C# 操作 Docx

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.69/5 (9投票s)

2010年6月14日

CPOL
viewsIcon

96623

使用 C# 和 OpenXML SDK 在没有 MSO 的情况下操作 docx。

引言

使用 OpenXML SDK,您可以在没有安装 Microsoft Word 的情况下编辑 docx 文件。

在这种特定情况下,我正在编辑 docx 文件的自定义属性,这些属性通常用于存储一些应用程序的信息以供进一步使用,甚至用于我们开发的某些插件。

使用代码

这篇文章非常简单;它的目的是传播信息,只是根据 MSDN 向您展示该怎么做。

首先,我的发现是在我找到了 OpenXML SDK,它允许我在没有在运行我的应用程序的服务器上安装 Office 的情况下操作 Word 文档,这是一个重大突破!我使用的添加自定义属性的代码来自 MSDN,我将在这里向您展示。

PropertyTypes 枚举

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

该方法

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;
}

用法

// 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);

这段代码的目的是写入自定义属性,以便我的 Word 插件能够正常工作,在大多数情况下都非常方便。

希望这对您来说与对我一样有价值!非常感谢。

© . All rights reserved.