Configurar una propiedad en un documento de procesamiento de texto
En este tema se muestra cómo usar las clases del SDK de Open XML para Office para establecer mediante programación una propiedad personalizada en un documento de procesamiento de texto. Incluye un método SetCustomProperty de ejemplo para ilustrar esta tarea.
El ejemplo de código también incluye una enumeración que define los posibles tipos de propiedades personalizadas. El método SetCustomProperty requiere que el usuario indique uno de estos valores cuando llame al método.
public enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
Almacenar propiedades personalizadas
Es importante comprender cómo se almacenan las propiedades personalizadas en un documento de procesamiento de texto. Puede usar la herramienta de productividad de para Microsoft Office, que se muestra en la figura 1, para averiguar cómo deben almacenarse. Esta herramienta permite abrir un documento y ver los elementos que contiene, así como la jerarquía entre ellos. En la figura 1 se muestra un documento de prueba después de ejecutar el código de la sección Llamar al método SetCustomProperty de este artículo. La herramienta muestra, en los paneles situados a la derecha, tanto el XML para el elemento, como el código C# reflejado que puede usar para generar el contenido del elemento.
Figura 1. Herramienta de productividad del SDK de Open XML para Microsoft Office
También se ha extraído el XML relevante, que se muestra a continuación para facilitar la lectura.
<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>
Si examina el contenido XML, verá lo siguiente:
- Cada propiedad del contenido XML consta de un elemento XML que incluye el nombre y el valor de la propiedad.
- Para cada propiedad, el contenido XML incluye un atributo fmtid, que siempre está establecido en el mismo valor de cadena:
{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
. - Cada propiedad del contenido XML incluye un atributo pid, que debe incluir un entero que empiece con 2 para la primera propiedad y debe incrementar para cada propiedad sucesiva.
- Cada propiedad realiza un seguimiento de su tipo (en la figura, los nombres de elemento vt:lpwstr y vt:filetime definen los tipos de cada propiedad).
El método de ejemplo proporcionado incluye el código necesario para crear o modificar una propiedad de documento personalizada en un documento de Microsoft Word 2013 o Microsoft Word 2010. Encontrará el listado de código completo para el método en la sección Código de ejemplo.
Método SetCustomProperty
Use el método SetCustomProperty para establecer una propiedad personalizada en un documento de procesamiento de texto. El método SetCustomProperty acepta cuatro parámetros:
El nombre del documento que se desea transformar (cadena).
El nombre de la propiedad que se desea agregar o modificar (cadena).
El valor de la propiedad (objeto).
El tipo de propiedad (uno de los valores de la enumeración PropertyTypes).
public static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
Llamar al método SetCustomProperty
El método SetCustomProperty permite definir una propiedad personalizada y devuelve el valor actual de la propiedad, si existe. Para llamar al método de ejemplo, transmita el nombre del archivo, el nombre de la propiedad, el valor de la propiedad y los parámetros de tipo de la propiedad. En el código de ejemplo siguiente se muestra un ejemplo.
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));
Tras la ejecución de este código, use el procedimiento siguiente para ver las propiedades personalizadas desde Word.
- Abra el archivo SetCustomProperty.docx en Word.
- En la pestaña Archivo, haga clic en información.
- Haga clic en Propiedades.
- Haga clic en Propiedades avanzadas.
Se mostrarán las propiedades avanzadas en el cuadro de diálogo que aparece, tal como se muestra en la figura 2.
Figura 2. Propiedades personalizadas en el cuadro de diálogo Propiedades avanzadas
Funcionamiento del código
El método SetCustomProperty empieza configurando algunas variables internas. A continuación, examina la información sobre la propiedad y crea una nueva CustomDocumentProperty basada en los parámetros especificados. El código también mantiene una variable llamada propSet para indicar si se ha creado correctamente el nuevo objeto de propiedad. Este código comprueba el tipo de valor de propiedad y, a continuación, convierte la entrada al tipo correcto, definiendo la propiedad apropiada del objeto CustomDocumentProperty.
Nota:
[!NOTA] El tipo CustomDocumentProperty funciona de forma muy parecida a un tipo de variante de VBA. Mantiene marcadores de posición independiente como propiedades para los distintos tipos de datos que pueda contener.
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");
}
En este momento, si el código no ha producido una excepción, puede suponer que la propiedad es válida y el código establece las propiedades FormatId y Name de la nueva propiedad personalizada.
// Now that you have handled the parameters, start
// working on the document.
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;
Trabajar con el documento
Una vez proporcionado el objeto CustomDocumentProperty, el código interactúa con el documento proporcionado en los parámetros para el procedimiento SetCustomProperty. El código comienza abriendo el documento en modo de lectura y escritura mediante el método Open de la clase WordprocessingDocument . El código intenta recuperar una referencia a la parte de propiedades de archivo personalizado mediante la propiedad CustomFilePropertiesPart del documento.
using (var document = WordprocessingDocument.Open(fileName, true))
{
var customProps = document.CustomFilePropertiesPart;
// Code removed here...
}
Si el código no encuentra una parte de propiedades personalizadas, crea una parte nueva y le agrega un nuevo conjunto de propiedades.
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();
}
A continuación, el código recupera una referencia a la propiedad Properties de la parte de propiedades personalizadas (es decir, una referencia a las propiedades en sí). Si el código necesita crear una nueva parte de propiedades personalizadas, significa que esta referencia no es nula. Sin embargo, en el caso de las partes de propiedades personalizadas existentes, es posible, aunque poco probable, que la propiedad Properties sea nula. En este caso, el código no podrá continuar.
var props = customProps.Properties;
if (props != null)
{
// Code removed here...
}
Si la propiedad ya existe, el código recupera su valor actual y luego elimina la propiedad. ¿Por qué eliminar la propiedad? Si el nuevo tipo de la propiedad coincidiera con el tipo existente, el código podría definir el valor de la propiedad como el nuevo valor. Por otro lado, si el nuevo tipo no coincide, el código debe crear un nuevo elemento y eliminar el antiguo (es el nombre del elemento el que define su tipo; para más información, vea la figura 1). Resulta más simple eliminar siempre y volver a crear el elemento. El código usa una consulta LINQ simple para buscar la primera coincidencia del nombre de la propiedad.
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();
}
Ahora puede estar seguro de que la parte de propiedades personalizadas existe, que no existe una propiedad con el mismo nombre que la nueva propiedad y que puede que haya otras propiedades personalizadas existentes. El código sigue los pasos que se indican a continuación:
Anexa la nueva propiedad como elemento secundario de la colección de propiedades.
Recorre en bucle todas las propiedades existentes y establece el atributo pid en valores crecientes, a partir de 2.
Guarda la 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 último, el código devuelve el valor de propiedad original almacenado.
return returnValue;
Código de ejemplo
A continuación se indica el ejemplo de código SetCustomProperty completo en C# y 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
}