Définir une propriété personnalisée dans un document de traitement de texte
Cette rubrique montre comment utiliser les classes du Kit de développement logiciel (SDK) Open XML pour Office afin de définir par programmation une propriété personnalisée dans un document de traitement de texte. Elle contient un exemple de méthode SetCustomProperty pour illustrer cette tâche.
L'exemple de code inclut également une énumération qui définit les types possibles de propriétés personnalisées. La méthode SetCustomProperty impose que vous fournissiez l'une de ces valeurs lorsque vous appelez la méthode.
public enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
Mode de stockage des propriétés personnalisées
Il est important de comprendre comment les propriétés personnalisées sont stockées dans un document de traitement de texte. Vous pouvez utiliser l’outil de productivité pour Microsoft Office, illustré dans la figure 1, pour découvrir comment ils sont stockés. Cet outil vous permet d'ouvrir un document et de voir ses parties et la hiérarchie de celles-ci. La figure 1 montre un document de test après l'exécution du code dans la section Appel de la méthode SetCustomProperty de cet article. L'outil affiche dans les volets de droite le XML de la partie et le code C# correspondant que vous pouvez utiliser pour générer le contenu de la partie.
Figure 1. Outil de productivité du Kit de développement logiciel (SDK) Open XML pour Microsoft Office
Le XML correspondant est également extrait et présenté pour simplifier la lecture.
<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 vous examinez le contenu XML, vous noterez les points suivants :
- Chaque propriété du contenu XML est composée d'un élément XML qui inclut le nom et la valeur de la propriété.
- Pour chaque propriété, le contenu XML inclut un attribut fmtid, qui est toujours réglé à la même valeur de chaîne :
{D5CDD505-2E9C-101B-9397-08002B2CF9AE}
. - Chaque propriété dans le contenu XML inclut un attribut pid, qui doit inclure un entier commençant à 2 pour la première propriété et s'incrémentant à chaque propriété successive.
- Chaque propriété suit son type (dans la figure, les noms d'éléments vt:lpwstr et vt:filetime définissent les types pour chaque propriété).
L’exemple de méthode fourni ici inclut le code requis pour créer ou modifier une propriété de document personnalisée dans un document Microsoft Word 2010 ou Microsoft Word 2013. Vous pouvez facilement trouver le listing du code complet pour la méthode dans la section Exemple de code.
Méthode SetCustomProperty
Utilisez la méthode SetCustomProperty pour définir une propriété personnalisée dans un document de traitement de texte. La méthode SetCustomProperty accepte quatre paramètres :
Le nom du document à modifier (chaîne).
Le nom de la propriété à ajouter ou à modifier (chaîne).
La valeur de la propriété (objet).
Le type de propriété (l'une des valeurs dans l'énumération PropertyTypes).
public static string SetCustomProperty(
string fileName,
string propertyName,
object propertyValue,
PropertyTypes propertyType)
Appel de la méthode SetCustomProperty
La méthode SetCustomProperty vous permet de définir une propriété personnalisée, et renvoie la valeur actuelle de la propriété, le cas échéant. Pour appeler l'exemple de méthode, passez le nom du fichier, le nom de la propriété, la valeur de la propriété et les paramètres de type de propriété. Le code suivant est fourni à titre d'exemple.
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));
Après l'exécution de ce code, utilisez la procédure suivante pour afficher les propriétés personnalisées dans Word.
- Ouvrez le fichier SetCustomProperty.docx dans Word.
- Dans l'onglet Fichier, cliquez sur Info.
- Cliquez sur Propriétés.
- Cliquez sur Propriétés avancées.
Les propriétés personnalisées s'affichent dans la boîte de dialogue qui apparaît, comme le montre la figure 2.
Figure 2. Propriétés personnalisées dans la boîte de dialogue Propriétés avancées
Fonctionnement du code
La méthode SetCustomProperty démarre en définissant des variables internes. Ensuite, il examine les informations sur la propriété et crée une propriété CustomDocumentProperty en fonction des paramètres que vous avez spécifiés. Le code maintient également une variable nommée propSet pour indiquer s’il a réussi ou non à créer le nouvel objet de propriété. Ce code vérifie le type de la valeur de propriété, puis convertit l'entrée dans le type approprié, définissant la propriété appropriée de l'objet CustomDocumentProperty.
Remarque
[!REMARQUE] Le type CustomDocumentProperty fonctionne de façon assez similaire à un type de variante VBA. Il maintient des espaces réservés séparés comme propriétés pour les divers types de données qu'il pourrait contenir.
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");
}
À ce stade, si le code n’a pas levée d’exception, vous pouvez supposer que la propriété est valide et que le code définit les propriétés FormatId et Name de la nouvelle propriété personnalisée.
// Now that you have handled the parameters, start
// working on the document.
newProp.FormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
newProp.Name = propertyName;
Travail sur le document
Avec l'objet CustomDocumentProperty, le code entre ensuite en interaction avec le document que vous avez fourni dans les paramètres à la procédure SetCustomProperty. Le code commence par ouvrir le document en mode lecture/écriture à l’aide de la méthode Open de la classe WordprocessingDocument . Le code tente de récupérer une référence à la partie des propriétés de fichier personnalisées à l’aide de la propriété CustomFilePropertiesPart du document.
using (var document = WordprocessingDocument.Open(fileName, true))
{
var customProps = document.CustomFilePropertiesPart;
// Code removed here...
}
Si le code ne peut pas trouver une partie de propriétés personnalisées, il crée une nouvelle partie, puis y ajoute un nouvel ensemble de propriétés.
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();
}
Ensuite, le code récupère une référence à la propriété Properties de la partie de propriétés personnalisées (c'est-à-dire une référence aux propriétés elles-mêmes). Si le code doit créer une nouvelle partie de propriétés personnalisées, vous savez que cette référence n'est pas nulle. Cependant, pour les parties de propriétés personnalisées existantes, il est possible, bien que hautement improbable, que la propriété Properties sera nulle. Si c'est le cas, le code ne peut pas continuer.
var props = customProps.Properties;
if (props != null)
{
// Code removed here...
}
Si la propriété existe déjà, le code récupère sa valeur actuelle, puis supprime la propriété. Pourquoi supprimer la propriété ? Si le nouveau type pour la propriété correspond au type existant pour la propriété, le code pourrait définir la valeur de la propriété à une nouvelle valeur. En revanche, si le nouveau type ne correspond pas, le code doit créer un nouvel élément, supprimant l'ancien (c'est le nom de l'élément qui définit son type, pour plus d'informations, voir la figure 1). Il est plus simple de toujours supprimer, puis recréer l'élément. Le code utilise une requête LINQ simple pour trouver la première correspondance pour le nom de la propriété.
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();
}
Maintenant, vous savez de façon certaine que la partie de propriétés personnalisées existe, qu'une propriété ayant le même nom que la nouvelle propriété n'existe pas, et que et qu'il pourrait y avoir d'autres propriétés personnalisées existantes. Le code effectue les opérations suivantes :
Ajoute la nouvelle propriété en tant qu'enfant de la collection de propriétés.
Effectue une boucle sur toutes les propriétés existantes et définit l’attribut pid sur des valeurs croissantes, en commençant à 2.
Enregistre la partie.
// 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();
Enfin, le code renvoie la valeur de propriété d'origine stockée.
return returnValue;
Exemple de code
Voici l'exemple de code SetCustomProperty complet en C# et 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
}