ワープロ ドキュメントでカスタム プロパティを設定する
このトピックでは、Open XML SDK for Office のクラスを使用して、プログラムによってワープロ ドキュメントのカスタム プロパティを設定する方法について説明します。 このタスクを示すメソッド SetCustomProperty
例が含まれています。
また、このサンプル コードには、カスタム プロパティで使用できるデータ型を定義する列挙型も含まれています。
SetCustomProperty
メソッドでは、 メソッドを呼び出すときに、これらの値のいずれかを指定する必要があります。
enum PropertyTypes : int
{
YesNo,
Text,
DateTime,
NumberInteger,
NumberDouble
}
カスタム プロパティの保存方法
ワープロ文書にカスタム プロパティの保存方法を理解しておくことは重要です。 図 1 に示す Productivity Tool for Microsoft Office を使用すると、このカスタム プロパティがどのように格納されているかがわかります。 このツールを使用すると、ドキュメントを開いて、ドキュメントのパーツと各パーツの階層構造を表示できます。 図 1 は、この記事の「SetCustomProperty メソッドを呼び出す」セクションのコードを実行した後のテスト ドキュメントを示しています。 このツールの右側のウィンドウには、パーツの XML と、そのパーツのコンテンツの生成に使用できる対応する C# コードが表示されています。
図 1. Open XML SDK Productivity Tool for 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
メソッドは、次の 4 つのパラメーターを受け入れます。
変更する文書の名前 (文字列)。
追加または変更するプロパティの名前 (文字列)。
プロパティの値 (オブジェクト)。
プロパティの種類 (
PropertyTypes
列挙内の値の 1 つ)。
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 からカスタム プロパティを表示します。
- Wordで .docx ファイルを開きます。
- [ ファイル] タブの [ 情報く] をクリックします。
- [ プロパティ] をクリックします。
- [ 詳細プロパティ] をクリックします。
図 2 に示すように、表示されたダイアログ ボックスにカスタム プロパティが表示されます。
図 2. [詳細プロパティ] ダイアログ ボックスのカスタム プロパティ
コードの動作のしくみ
SetCustomProperty
メソッドは、いくつかの内部変数を設定することから始まります。 次に、 プロパティに関する情報を調べ、指定したパラメーターに基づいて新しい CustomDocumentProperty を作成します。 また、このコードには、新しいプロパティ オブジェクトが正しく作成されたかどうかを示す propSet
という名前の変数もあります。 このコードでは、プロパティ値の型を検証し、入力を正しい型に変換し、 CustomDocumentProperty オブジェクトの適切なプロパティを設定します。
注:
CustomDocumentProperty型は VBA バリアント型とよく似ています。 これは、含まれる可能性があるさまざまな種類のデータのプロパティとして、個別のプレースホルダーを保持します。
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
プロシージャにパラメーターで指定したドキュメントと対話します。 コードは、WordprocessingDocument クラスの Open メソッドを使用して、ドキュメントを読み取り/書き込みモードで開くことから始まります。 コードは、ドキュメントの 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;
サンプル コード
C# と Visual Basic の完全な SetCustomProperty
コード サンプルを次に示します。
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;
}