使用 Open XML SDK 2.0 设置 Word 文档属性

作者:管理员 更新时间:2013-11-14 10:31

概述

利用 Open XML 文件格式,可以修改 Word 2007 或 Word 2010 文档中的自定义文档属性。Open XML SDK 2.0 添加了强类型类以简化对 Open XML 文件格式的访问。SDK 旨在简化修改自定义文档属性这一任务,此直观操作方法附带的代码示例演示了如何使用 SDK 来做到这一点。

此直观操作方法附带的示例代码可在 Word 2007 或 Word 2010 文档中创建和修改自定义文档属性。若要使用该示例代码,请通过“浏览”一节中列出的链接来安装 Open XML SDK 2.0。该示例代码将作为 Open XML SDK 2.0 的代码示例集的一部分包含。“浏览”一节还包括指向完整代码示例集的链接,但您无需下载并安装代码示例即可使用示例代码。

示例应用程序将修改您提供的文档中的自定义属性,并在示例中调用 WDSetCustomProperty方法来执行此操作。此方法使您能够设置一个自定义属性,并返回该属性的当前值(如果有)。对此方法的调用类似于以下代码示例。

C#

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

此示例代码还包括一个枚举,此枚举定义了各种可能的自定义属性类型:WDSetCustomProperty过程要求您在向其传递属性和值时提供这些值之一。

C#

publicenum PropertyTypes : int
{
  YesNo,
  Text,
  DateTime,
  NumberInteger,
  NumberDouble
}

了解在 Word 文档中存储自定义属性的方式很重要。Open XML SDK 2.0 在其工具目录中包含一个名为 OpenXmlSdkTool.exe 的有用应用程序,如图 1 所示。此工具使您能够打开一个文档,并查看该文档的各个部分及其层次结构。图 1 显示了在此示例中运行代码后的测试文档,在右窗格中,此工具显示了部分的 XML 以及可用来生成部分内容的反射 C# 代码。

图 1 显示的是 Open XML SDK 2.0 Productivity Tool,可利用此工具查看文档的 Open XML 内容。

图 1. Open XML SDK 2.0 Productivity Tool

如果您检查图 1 中的 XML 内容,您将发现与代码有关的信息类似于以下内容:

  • XML 内容中的每个属性均由一个 XML 元素构成,其中包含该属性的名称和值。

  • 对于每个属性 (Property),XML 内容包含一个 fmtid 属性 (Attribute),并且其始终设置为相同的字符串值:{D5CDD505-2E9C-101B-9397-08002B2CF9AE}

  • XML 内容中的每个属性 (Property) 均包含一个 pid 属性 (Attribute),它必须包含针对第一个属性 (Property) 的整数,该整数从 2 开始并针对每个后续属性 (Property) 递增。

  • 每个属性将跟踪其类型(在图中,vt:lpwstrvt:filetime 元素名称定义了每个属性的类型)。

编码

此直观操作方法附带的示例代码包括在 Word 2007 或 Word 2010 文档中创建或修改自定义文档属性所需的代码。

设置引用

若要使用 Open XML SDK 2.0 中的代码,您必须向您的项目添加多个引用。虽然示例项目包含这些引用,但您必须在您的代码中显式引用以下程序集:

  • WindowsBase - 可以根据您创建的项目的类型为您设置此引用。

  • DocumentFormat.OpenXml - 由 Open XML SDK 2.0 安装。

您还应将下面的 using/Imports 语句添加到代码文件的顶部。

C#

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

检查过程

WDSetCustomProperty 过程接受四个参数:

  • 要修改的文档的名称(字符串)。

  • 要添加或修改的属性的名称(字符串)

  • 属性的值(对象)

  • 属性的类型(PropertyTypes 枚举中的一个值)

C#

conststring fileName = "C:\\temp\\test.docx";
Console.WriteLine("Manager = " + 
  WDSetCustomProperty(fileName, "Manager", "Peter", 
    PropertyTypes.Text));

处理过程参数

WDSetCustomProperty 过程首先会设置一些内部变量。接下来,它会检查与属性有关的信息,并根据您指定的参数创建新 CustomDocumentProperty。代码还将保留一个名为 propSet 的变量以指示其是否成功创建该新属性对象。此代码将验证属性值的类型,然后将输入转换为正确的类型,并设置 CustomDocumentProperty 对象的适当属性。

注释:

CustomDocumentProperty 类型的工作方式与 VBA Variant 类型的工作方式类似。它将单独的占位符作为其可能包含的各种数据类型的属性保留。

C#

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) isint) { newProp.VTInt32 = new VTInt32(propertyValue.ToString()); propSet = true; }break; case PropertyTypes.NumberDouble:if (propertyValue isdouble) { 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 isbool) { // 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

InvalidDataException("propertyValue");

}

此时,如果代码尚未引发异常,则您可假定属性有效且代码将设置新自定义属性的FormatIdName属性。

C#

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

使用文档

在给定 CustomDocumentProperty 对象的情况下,代码紧接着会与您在参数中提供给WDSetCustomProperty

过程的文档进行交互。代码首先会通过使用WordProcessingDocument 类的 Open 方法在读写模

式下打开 Word 文档。代码将尝试使用文档的 CustomFilePropertiesPart 属性来检索对自定

义文件属性部分的引用。

C#

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

如果代码无法找到自定义属性部分,则它会创建一个新部分,并向该部分添加一组新属性。

C#

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

接下来,代码会检索对自定义属性部分的 Properties 属性的引用(即,对属性本身的引用)。

如果代码必须创建一个新的自定义属性部分,则您会知道此引用不为 Null;但对于现有

自定义属性部分,Properties 属性可能为 Null(但可能性不大)。如果出现此情况,

则代码将无法继续运行。

C#

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

很难对下一个步骤进行解释。如果属性已存在,则代码将检索其当前值,然后删除属性。为何要删

除属性呢?如果属性的新类型与属性的现有类型匹配,则代码可将属性的值设置为新值。另一方面,

如果新类型不匹配,则代码必须创建一个新元素,并删除原有元素(它是定义其类型的元素的

名称 - 有关详细信息,请见图 1)。始终删除并重新创建元素会更为简单。代码使用一个

简单 LINQ 查询来查找属性名的第一个匹配项。

C#

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

现在,您将确切知道存在自定义属性部分,不存在具有与新属性相同的名称的属性,并且可能存在其他

现有自定义属性。代码将执行以下步骤:

  1. 将新属性作为属性集合的子集追加。

  2. 循环访问所有现有属性 (Property),并将 pid 属性 (Attribute) 设置递增的值(从 2 开始)。

  3. 保存部分。

C#

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

最后,代码将返回存储的原始属性值。

C#

return returnValue;

提供测试文档并运行示例代码。在 Word 2007 或 Word 2010 中加载已经修改的文档,并查看自定义文档

属性(或者,您可以将文档加载到 Open XML SDK Productivity Tool 中并查看该部分),确认结果与

图 1 中所示的结果匹配。

示例过程

示例过程包括以下代码。

C#

publicstaticstring 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) isint) { newProp.VTInt32 = new VTInt32(propertyValue.ToString()); propSet = true; }break; case PropertyTypes.NumberDouble:if (propertyValue isdouble) { 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 isbool) { // 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:

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

}

读取此直观操作方法中的代码示例包括您在使用 Open XML SDK 2.0 时将遇到的许多问题。虽然每个示例

略有不同,但基本概念都是相同的。除非您了解尝试使用的部分的结构,否则,您即使使用

Open XML SDK 2.0 也无法与该部分进行交互。在开始编写代码之前,花时间研究您使用的对象,

这将为您节省时间。

来源:亦有软件
软件产品Eysln Software Product
亦有公告Eysln Notice
案例中心Eysln Template
知识库Eysln Knowledge Base
工具箱Eysln Toolkit Online