Установите пользовательское свойство в текстовом редакторе
В этом разделе показано, как использовать классы в пакете SDK Open XML для Office для программного задания пользовательского свойства в текстовом документе. Он содержит пример SetCustomProperty
метода для иллюстрации этой задачи.
Пример кода также включает в себя перечисление, которое определяет возможные типы настраиваемых свойств. При вызове SetCustomProperty
метода необходимо указать одно из этих значений.
enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
Формат хранения настраиваемых свойств
Важно понимать, как пользовательские свойства хранятся в текстовом документе. Чтобы узнать, как они хранятся, можно использовать средство повышения производительности для Microsoft Office, показанное на рис. 1. Это средство позволяет открыть документ и просмотреть его части и иерархию частей. На рисунке 1 показан тестовый документ после выполнения кода в разделе Вызов метода SetCustomProperty этой статьи. Средство отображает в правой области XML для части и отраженный код C#, который можно использовать для создания содержимого части.
Рис. 1. Средство повышения производительности пакета SDK Open XML для Microsoft Office
Соответствующий XML-код также извлечен и показан ниже для удобства.
<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>
Если посмотреть на XML-содержимое, можно заметить следующее:
- Каждое свойство в XML-содержимом состоит из XML-элемента, включающего имя и значение свойства.
- Для каждого свойства XML-содержимое включает
fmtid
атрибут, для которого всегда задано одно и то же строковое значение:{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
. - Каждое свойство в XML-содержимом
pid
содержит атрибут, который должен содержать целое число, начинающееся с 2 для первого свойства и приращение для каждого последующего свойства. - Каждое свойство отслеживает свой тип (на рисунке
vt:lpwstr
имена элементов иvt:filetime
определяют типы для каждого свойства).
Приведенный здесь пример метода содержит код, необходимый для создания или изменения пользовательского свойства документа в документе Microsoft Word. Полный листинг кода для этого метода приведен в разделе Пример кода.
Метод SetCustomProperty
Используйте метод , SetCustomProperty
чтобы задать пользовательское свойство в текстовом документе. Метод SetCustomProperty
принимает четыре параметра:
Имя изменяемого документа (строковое значение).
Имя изменяемого (или добавляемого) свойства (строковое значение).
Значение свойства (объект).
Тип свойства (одно из значений в перечислении
PropertyTypes
).
static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
Вызов метода SetCustomProperty
Метод SetCustomProperty
позволяет задать пользовательское свойство и возвращает текущее значение свойства, если оно существует. Чтобы вызвать пример метода, передайте имя файла, имя свойства, значение свойства и параметры типа свойства. Следующий код показывает пример использования:
string fileName = args[0];
Console.WriteLine(string.Join("Manager = ", SetCustomProperty(fileName, "Manager", "Pedro", PropertyTypes.Text)));
Console.WriteLine(string.Join("Manager = ", SetCustomProperty(fileName, "Manager", "Bonnie", PropertyTypes.Text)));
Console.WriteLine(string.Join("ReviewDate = ", SetCustomProperty(fileName, "ReviewDate", DateTime.Parse("01/26/2024"), PropertyTypes.DateTime)));
После выполнения этого кода используйте следующую процедуру для просмотра настраиваемых свойств в приложении Word.
- Откройте файл .docx в Word.
- На вкладке Файл выберите команду Сведения.
- Щелкните Свойства.
- Выберите команду Дополнительные свойства.
Настраиваемые свойства будут отображены в появившемся диалоговом окне, как показано на рис. 2.
Рис. 2. Пользовательские свойства в диалоговом окне "Дополнительные свойства"
Принципы работы кода
Метод SetCustomProperty
начинается с настройки некоторых внутренних переменных. Затем он проверяет сведения о свойстве и создает новый CustomDocumentProperty объект на основе указанных параметров. Код также содержит переменную с именем propSet
, указывающую, успешно ли создан новый объект свойства. Этот код проверяет тип значения свойства, а затем преобразует входные данные в правильный тип, задавая соответствующее CustomDocumentProperty свойство объекта .
Примечание.
Тип CustomDocumentProperty работает так же, как тип VBA Variant. В нем имеются отдельные заполнители, используемые в качестве свойств для различных типов данных, которые он может содержать.
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");
}
На этом этапе, если код не создал исключение, можно предположить, что свойство допустимо, и код задает FormatId свойства и Name нового настраиваемого свойства.
// Now that you have handled the parameters, start
// working on the document.
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;
Работа с документом
С учетом CustomDocumentProperty объекта код взаимодействует с документом, предоставленным в параметрах SetCustomProperty
процедуры. Код начинается с открытия документа в режиме Open чтения и записи с помощью метода WordprocessingDocument класса . Код пытается получить ссылку на часть настраиваемых свойств файла с помощью CustomFilePropertiesPart свойства документа.
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();
}
Затем код извлекает ссылку Properties на свойство части настраиваемых свойств (то есть ссылку на сами свойства). Если бы перед кодом стояла задача создания новой части для настраиваемых свойств, то, как вы скорее всего знаете, есть гарантия, что эта ссылка не будет иметь значение null. Однако для существующих частей настраиваемых свойств возможно, хотя и весьма маловероятно, что Properties свойство будет иметь значение NULL. И если это так, код не сможет продолжить работу.
var props = customProps.Properties;
if (props is not null)
{
Если свойство уже существует, код получает его текущее значение, а затем удаляет свойство . Зачем удалять свойство? Если новый тип свойства соответствует существующему типу свойства, код может задать для свойства новое значение. С другой стороны, если новый тип не совпадает, код должен создать новый элемент, удалив старый (это имя элемента, определяющего его тип. Дополнительные сведения см. на рис. 1). Проще всегда удалять, а затем повторно создавать элемент. Код использует простой запрос LINQ для поиска первого соответствия имени свойства.
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();
}
Теперь можно быть уверенным, что часть для настраиваемого свойства существует; что свойство с именем, совпадающим с новым свойством, не существует; и что могут уже существовать и другие настраиваемые свойства. Код выполняет следующие действия:
Добавляет новое свойство как дочерний объект в коллекцию свойств.
Циклически просматривает все существующие свойства и задает
pid
атрибуту увеличивающиеся значения, начиная с 2.Сохраняет часть.
// 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++;
}
Наконец, код возвращает исходное сохраненное ранее значение свойства.
return returnValue;
Пример кода
Ниже приведен полный SetCustomProperty
пример кода на C# и Visual Basic.
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();
}
// 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++;
}
}
}
return returnValue;
}