Festlegen einer benutzerdefinierten Eigenschaft in einem Textverarbeitungsdokument
In diesem Thema wird gezeigt, wie Sie die Klassen im Open XML SDK für Office verwenden, um eine benutzerdefinierte Eigenschaft in einem Textverarbeitungsdokument programmgesteuert festzulegen. Zum Veranschaulichen dieser Aufgabe wird ein Beispiel der SetCustomProperty -Methode vorgestellt.
Der Beispielcode enthält außerdem eine Aufzählung, die die möglichen Typen benutzerdefinierter Eigenschaften bestimmt. Die SetCustomProperty-Methode erfordert die Angabe eines dieser Werte, wenn Sie die Methode aufrufen.
public enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
Wie werden benutzerdefinierte Eigenschaften gespeichert?
Sie sollten wissen, wie benutzerdefinierte Eigenschaften in einem Textverarbeitungsdokument gespeichert werden. Sie können das Produktivitätstool für Microsoft Office (abbildung 1) verwenden, um zu ermitteln, wie sie gespeichert werden. Mit diesem Tool können Sie ein Dokument öffnen und seine Teile sowie deren Hierarchie anzuzeigen. Abbildung 1 zeigt ein Testdokument nach Ausführung des Codes im Abschnitt Aufrufen der SetCustomProperty-Methode in diesem Artikel. Das Tool zeigt in den rechten Bereichen sowohl die XML für den Teil als auch den entsprechenden C#-Code, mit dem Sie den Inhalt des Teils generieren können.
Abbildung 1: Open XML SDK Productivity Tool für Microsoft Office
Der entsprechende XML-Code wurde ebenfalls extrahiert und wird hier zum Vereinfachen des Lesens gezeigt.
<op:Properties xmlns:vt="https://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes" xmlns:op="https://schemas.openxmlformats.org/officeDocument/2006/custom-properties">
<op:property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="2" name="Manager">
<vt:lpwstr>Mary</vt:lpwstr>
</op:property>
<op:property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="3" name="ReviewDate">
<vt:filetime>2010-12-21T00:00:00Z</vt:filetime>
</op:property>
</op:Properties>
Beim Untersuchen des XML-Inhalts werden Sie Folgendes feststellen:
- Jede Eigenschaft im XML-Inhalt besteht aus einem XML-Element, das den Namen und den Wert der Eigenschaft enthält.
- Für jede Eigenschaft enthält der XML-Inhalt ein fmtid-Attribut, das stets auf denselben Zeichenfolgenwert festgelegt ist:
{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
. - Jede Eigenschaft im XML-Inhalt enthält ein pid-Attribut, das für die erste Eigenschaft eine mit 2 beginnende ganze Zahl aufweist, die für jede nachfolgende Eigenschaft erhöht wird.
- Jede Eigenschaft verfolgt ihren Typ nach (in der Abbildung definieren die Elementnamen vt:lpwstr und vt:filetime den Typ jeder Eigenschaft).
Die hier bereitgestellte Beispielmethode enthält den Code, der zum Erstellen oder Ändern einer benutzerdefinierten Dokumenteigenschaft in einem Microsoft Word 2010- oder Microsoft Word 2013-Dokument erforderlich ist. Den gesamten Code für diese Methode finden Sie im Abschnitt Beispielcode.
SetCustomProperty-Methode
Verwenden Sie die SetCustomProperty-Methode , um eine benutzerdefinierte Eigenschaft in einem Textverarbeitungsdokument festzulegen. Die SetCustomProperty-Methode akzeptiert vier Parameter:
Den Namen des zu ändernden Dokuments (Zeichenfolge)
Den Namen der hinzuzufügenden oder zu ändernden Eigenschaft (Zeichenfolge)
Den Wert der Eigenschaft (Objekt)
Die Art der Eigenschaft (einen der Werte in der PropertyTypes-Aufzählung)
public static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
Aufrufen der SetCustomProperty-Methode
Die SetCustomProperty-Methode ermöglicht das Festlegen einer benutzerdefinierter Eigenschaft und gibt den aktuellen Wert der Eigenschaft zurück, sofern vorhanden. Übergeben Sie zum Aufrufen der Beispielmethode die Parameter Dateiname, Eigenschaftenname, Eigenschaftenwert und Eigenschaftentyp. Der folgende Code zeigt ein Beispiel.
const string fileName = @"C:\Users\Public\Documents\SetCustomProperty.docx";
Console.WriteLine("Manager = " +
SetCustomProperty(fileName, "Manager", "Peter", PropertyTypes.Text));
Console.WriteLine("Manager = " +
SetCustomProperty(fileName, "Manager", "Mary", PropertyTypes.Text));
Console.WriteLine("ReviewDate = " +
SetCustomProperty(fileName, "ReviewDate",
DateTime.Parse("12/21/2010"), PropertyTypes.DateTime));
Zeigen Sie nach der Ausführung dieses Codes mithilfe der folgenden Prozedur die benutzerdefinierten Eigenschaften in Word an.
- Öffnen Sie die Datei SetCustomProperty.docx in Word.
- Klicken Sie auf der Registerkarte Datei auf Info.
- Klicken Sie auf Eigenschaften.
- Klicken Sie auf Erweiterte Eigenschaften.
Die benutzerdefinierten Eigenschaften werden im eingeblendeten Dialogfeld angezeigt (siehe Abbildung 2).
Abbildung 2: Benutzerdefinierte Eigenschaften im Dialogfeld "Erweiterte Eigenschaften"
Funktionsweise des Codes
Die SetCustomProperty-Methode legt zunächst einige interne Variablen fest. Als Nächstes werden die Informationen zur -Eigenschaft untersucht und basierend auf den von Ihnen angegebenen Parametern eine neue CustomDocumentProperty-Eigenschaft erstellt. Der Code verwaltet auch die Variable propSet, um anzugeben, ob das neue Eigenschaftsobjekt erfolgreich erstellt wurde. Dieser Code überprüft den Typ des Eigenschaftswerts und wandelt anschließend die Eingabe in den ordnungsgemäßen Typ um, wobei die entsprechende Eigenschaft des CustomDocumentProperty-Objekts festgelegt wird.
Hinweis
Der CustomDocumentProperty-Typ funktioniert ähnlich wie ein VBA Variant-Typ. Es verwaltet separate Platzhalter als Eigenschaften für die verschiedenen Datentypen, die darin enthalten sein können.
string returnValue = null;
var newProp = new CustomDocumentProperty();
bool propSet = false;
// Calculate the correct type.
switch (propertyType)
{
case PropertyTypes.DateTime:
// Be 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) 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 was not able to convert the
// property to a valid value, throw an exception.
throw new InvalidDataException("propertyValue");
}
Wenn der Code an diesem Punkt keine Ausnahme ausgelöst hat, können Sie davon ausgehen, dass die Eigenschaft gültig ist, und der Code legt die Eigenschaften FormatId und Name der neuen benutzerdefinierten Eigenschaft fest.
// Now that you have handled the parameters, start
// working on the document.
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;
Arbeiten mit dem Dokument
Bei angegebenem CustomDocumentProperty-Objekt interagiert der Code als Nächstes mit dem Dokument, das Sie in den Parametern für die SetCustomProperty-Prozedur angegeben haben. Der Code beginnt mit dem Öffnen des Dokuments im Lese-/Schreibmodus mithilfe der Open-Methode der WordprocessingDocument-Klasse . Der Code versucht, mithilfe der CustomFilePropertiesPart-Eigenschaft des Dokuments einen Verweis auf den Benutzerdefinierten Dateieigenschaftenteil abzurufen.
using (var document = WordprocessingDocument.Open(fileName, true))
{
var customProps = document.CustomFilePropertiesPart;
// Code removed here...
}
Wenn der Code den Teil mit den benutzerdefinierten Eigenschaften nicht findet, wird ein neuer Teil erstellt, dem eine neue Gruppe mit Eigenschaften hinzugefügt wird.
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();
}
Als Nächstes ruft der Code einen Verweis auf die Properties -Eigenschaft des Teils mit den benutzerdefinierten Eigenschaften (also einen Verweis auf die Eigenschaften selbst) ab. Wenn der Code einen neuen Teil mit benutzerdefinierten Eigenschaften erstellen muss, wissen Sie, dass dieser Verweis kein Null-Verweis ist. Für vorhandene Teile mit benutzerdefinierten Eigenschaften ist es jedoch möglich, wenngleich unwahrscheinlich, dass die Properties-Eigenschaft einen Null-Wert hat. Falls doch, kann der Code nicht fortgesetzt werden.
var props = customProps.Properties;
if (props != null)
{
// Code removed here...
}
Wenn die Eigenschaft bereits vorhanden ist, ruft der Code ihren aktuellen Wert ab und löscht anschließend die Eigenschaft. Warum wird die Eigenschaft gelöscht? Wenn der neue Typ der Eigenschaft ihrem vorhandenen Typ entspricht, könnte der Code den Wert der Eigenschaft auf den neuen Wert festlegen. Wenn dagegen der neue Typ nicht übereinstimmt, muss der Code ein neues Element erstellen und das alte löschen (der Typ des Elements wird durch seinen Namen bestimmt. Weitere Informationen finden Sie in Abbildung 1.) Es ist stets einfacher, das Element zu löschen und anschließend neu zu erstellen. Der Code verwendet eine einfache LINQ-Abfrage, um die erste Übereinstimmung für den Eigenschaftennamen zu finden.
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();
}
Nun wissen Sie mit Sicherheit, dass der Teil mit der benutzerdefinierten Eigenschaft vorhanden ist, dass keine Eigenschaft vorhanden ist, die denselben Namen wie die neue Eigenschaft hat, und dass es möglicherweise andere vorhandene benutzerdefinierte Eigenschaften gibt. Der Code führt die folgenden Schritte aus:
Fügt die neue Eigenschaft als untergeordnete Eigenschaft an die Eigenschaftenauflistung an.
Durchläuft alle vorhandenen Eigenschaften und legt das pid-Attribut auf steigende Werte ab 2 fest.
Speichert den Teil.
// Append the new property, and
// fix up 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();
Schließlich gibt der Code den gespeicherten Wert der ursprünglichen Eigenschaft zurück.
return returnValue;
Beispielcode
Es folgt das vollständige SetCustomProperty-Codebeispiel in C# und Visual Basic.
using DocumentFormat.OpenXml.CustomProperties;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.VariantTypes;
using System;
using System.IO;
using System.Linq;
static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
{
// Given a document name, a property name/value, and the property type,
// add a custom property to a document. The method returns the original
// value, if it existed.
string? returnValue = string.Empty;
var newProp = new CustomDocumentProperty();
bool propSet = false;
string? propertyValueString = propertyValue.ToString() ?? throw new System.ArgumentNullException("propertyValue can't be converted to a string.");
// Calculate the correct type.
switch (propertyType)
{
case PropertyTypes.DateTime:
// Be 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) 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(propertyValueString);
propSet = true;
}
break;
case PropertyTypes.NumberDouble:
if (propertyValue is double)
{
newProp.VTFloat = new VTFloat(propertyValueString);
propSet = true;
}
break;
case PropertyTypes.Text:
newProp.VTLPWSTR = new VTLPWSTR(propertyValueString);
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 was not able to convert the
// property to a valid value, throw an exception.
throw new InvalidDataException("propertyValue");
}
// Now that you have handled the parameters, start
// working 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 is null)
{
// No custom properties? Add the part, and the
// collection of properties now.
customProps = document.AddCustomFilePropertiesPart();
customProps.Properties = new Properties();
}
var props = customProps.Properties;
if (props is not null)
{
// This will trigger an exception if the property's Name
// property is null, but if that happens, the property is damaged,
// and probably should raise an exception.
var prop = props.FirstOrDefault(p => ((CustomDocumentProperty)p).Name!.Value == propertyName);
// Does the property exist? If so, get the return value,
// and then delete the property.
if (prop is not null)
{
returnValue = prop.InnerText;
prop.Remove();
}
else
{
throw new System.ArgumentException("propertyName property was not found or damaged.");
}
// Append the new property, and
// fix up 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;
}
enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}