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






4.69/5 (9投票s)
使用 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 插件能够正常工作,在大多数情况下都非常方便。
希望这对您来说与对我一样有价值!非常感谢。