Definir uma propriedade personalizada em um documento de processamento de texto
Este tópico mostra como usar as classes no SDK do Open XML para Office para definir programaticamente uma propriedade personalizada em um documento de processamento de palavras. Ele contém um método SetCustomProperty de exemplo para ilustrar essa tarefa.
O código de exemplo também inclui uma enumeração que define os possíveis tipos de propriedades personalizadas. O método SetCustomProperty exige que você forneça um desses valores ao chamar o método.
public enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
Como as propriedades personalizadas são armazenadas
É importante entender como as propriedades personalizadas são armazenadas em um documento de processamento de palavras. Você pode usar a Ferramenta de Produtividade do Microsoft Office, mostrada na Figura 1, para descobrir como elas são armazenadas. Essa ferramenta permite que você abra um documento e exiba suas partes e a hierarquia de partes. A Figura 1 mostra um documento de teste depois de executar o código na seção Chamando o Método SetCustomProperty deste artigo. A ferramenta é exibida nos painéis direitos do XML da parte e do código C# refletido que você pode usar para gerar o conteúdo da parte.
Figura 1. Abrir a Ferramenta de Produtividade do SDK do XML para o Microsoft Office
O XML relevante também é extraído e mostrado aqui para facilitar a leitura.
<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>
Se você examinar o conteúdo XML, encontrará o seguinte:
- Cada propriedade no conteúdo XML consiste em um elemento XML que inclui o nome e o valor da propriedade.
- Para cada propriedade, o conteúdo XML inclui um atributo fmtid , que é sempre definido como o mesmo valor de cadeia de caracteres:
{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
. - Cada propriedade no conteúdo XML inclui um atributo pid , que deve incluir um inteiro começando em 2 para a primeira propriedade e incrementando para cada propriedade sucessiva.
- Cada propriedade rastreia seu tipo (na figura, os nomes de elementos vt:lpwstr e vt:filetime definem os tipos para cada propriedade).
O método de exemplo fornecido aqui inclui o código necessário para criar ou modificar uma propriedade de documento personalizada em um documento Microsoft Word 2010 ou Microsoft Word 2013. Você pode encontrar a listagem de código completa para o método na seção Código de Exemplo .
Método SetCustomProperty
Use o método SetCustomProperty para definir uma propriedade personalizada em um documento de processamento de palavras. O método SetCustomProperty aceita quatro parâmetros:
O nome do documento a ser modificado (cadeia de caracteres).
O nome da propriedade a ser adicionada ou modificada (cadeia de caracteres).
O valor da propriedade (objeto).
O tipo de propriedade (um dos valores na enumeração PropertyTypes ).
public static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
Chamando o método SetCustomProperty
O método SetCustomProperty permite que você defina uma propriedade personalizada e retorna o valor atual da propriedade, se ela existir. Para chamar o método de exemplo, passe os parâmetros nome do arquivo, nome da propriedade, valor da propriedade e tipo de propriedade. O código de exemplo a seguir mostra um exemplo.
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));
Depois de executar esse código, use o procedimento a seguir para exibir as propriedades personalizadas de Word.
- Abra o arquivo SetCustomProperty.docx no Word.
- Na guia Arquivo , clique em Informações.
- Clique em Propriedades.
- Clique em Propriedades Avançadas.
As propriedades personalizadas serão exibidas na caixa de diálogo exibida, conforme mostrado na Figura 2.
Figura 2. Propriedades Personalizadas na caixa de diálogo Propriedades Avançadas
Como o código funciona
O método SetCustomProperty começa configurando algumas variáveis internas. Em seguida, ele examina as informações sobre a propriedade e cria um novo CustomDocumentProperty com base nos parâmetros especificados. O código também mantém uma variável chamada propSet para indicar se ele criou com êxito o novo objeto de propriedade. Esse código verifica o tipo do valor da propriedade e converte a entrada no tipo correto, definindo a propriedade apropriada do objeto CustomDocumentProperty .
Observação
O tipo CustomDocumentProperty funciona muito parecido com um tipo de Variante VBA. Ele mantém espaços reservados separados como propriedades para os vários tipos de dados que pode conter.
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");
}
Neste ponto, se o código não tiver gerado uma exceção, você poderá assumir que a propriedade é válida e o código define as propriedades FormatId e Name da nova propriedade personalizada.
// Now that you have handled the parameters, start
// working on the document.
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;
Trabalhando com o Documento
Dado o objeto CustomDocumentProperty , o código em seguida interage com o documento fornecido nos parâmetros para o procedimento SetCustomProperty . O código começa abrindo o documento no modo de leitura/gravação usando o método Open da classe WordprocessingDocument . O código tenta recuperar uma referência à parte de propriedades de arquivo personalizadas usando a propriedade CustomFilePropertiesPart do documento.
using (var document = WordprocessingDocument.Open(fileName, true))
{
var customProps = document.CustomFilePropertiesPart;
// Code removed here...
}
Se o código não puder encontrar uma parte de propriedades personalizadas, ele criará uma nova parte e adicionará um novo conjunto de propriedades à parte.
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();
}
Em seguida, o código recupera uma referência à propriedade Properties da parte de propriedades personalizadas (ou seja, uma referência às próprias propriedades). Se o código tiver que criar uma nova parte de propriedades personalizadas, você saberá que essa referência não é nula. No entanto, para as partes de propriedades personalizadas existentes, é possível, embora altamente improvável, que a propriedade Properties seja nula. Nesse caso, o código não poderá continuar.
var props = customProps.Properties;
if (props != null)
{
// Code removed here...
}
Se a propriedade já existir, o código recuperará seu valor atual e excluirá a propriedade. Por que excluir a propriedade? Se o novo tipo da propriedade corresponder ao tipo existente para a propriedade, o código poderá definir o valor da propriedade como o novo valor. Por outro lado, se o novo tipo não corresponder, o código deverá criar um novo elemento, excluindo o antigo (é o nome do elemento que define seu tipo— para obter mais informações, consulte Figura 1). É mais simples sempre excluir e recriar o elemento. O código usa uma consulta LINQ simples para localizar a primeira correspondência para o nome da propriedade.
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();
}
Agora, você saberá com certeza que a parte da propriedade personalizada existe, uma propriedade que tem o mesmo nome que a nova propriedade não existe e que pode haver outras propriedades personalizadas existentes. O código executa as seguintes etapas:
Acrescenta a nova propriedade como filho da coleção de propriedades.
Percorre todas as propriedades existentes e define o atributo pid como valores crescentes, começando em 2.
Salva a parte.
// 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();
Por fim, o código retorna o valor da propriedade original armazenada.
return returnValue;
Código de exemplo
A seguir está o exemplo completo de código SetCustomProperty em C# e 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
}