Bodu.IO.Compound.PropertySets Namespace
- Package
-
Bodu.IO.Compound 1.0.0
Purpose
Bodu.IO.Compound.PropertySets reads and writes OLE property sets - the code-paged, sectioned metadata streams (MS-OLEPS) a compound file stores under well-known names such as \x05SummaryInformation and \x05DocumentSummaryInformation. It offers two levels: the raw model (OlePropertySet, its OlePropertySection list, and the PROPVARIANT-shaped OlePropertyValue) for any property set, and typed views (SummaryInformation, DocumentSummaryInformation) with matching builders for the two standard document-metadata sets.
A property set is a header declaring a class identifier followed by one or two sections, each identified by a format identifier (FMTID) and holding properties keyed by an integer property identifier (PID). The first section carries the well-known properties; an optional second, user-defined section carries custom properties whose human-readable names live in the section's dictionary. Reading and writing are symmetric - the writer emits every value shape the reader parses, including vector (VT_VECTOR) values - so a set read from a real document can be modified and written back.
Static documentation
- Reading property sets - the typed views, the raw set, and writing sets back.
- Authoring compound files - embedding an authored set in a new container (pattern 5).
- Bodu.IO.Compound introduction and core concepts - where the property-set streams sit in the container.
Key types
Raw model
- OlePropertySet - the whole set. Read with
Parse(ReadOnlyMemory<byte>)orRead(Stream)(both throw CompoundFileFormatException on a malformed set); author withnew OlePropertySet(formatId, classId, codePage)andAddSection.FormatId,ClassId,CodePage,Sections;TryGetValue(propertyId, out value)and the indexer[propertyId]read the first section;ToArray(),WriteTo(Stream),WriteTo(IBufferWriter<byte>)serialize. - OlePropertySection - one section.
new OlePropertySection(formatId, codePage);FormatId,CodePage,Properties(PID → value),PropertyNames(PID → name, the dictionary of a user-defined section);TryGetValue/ indexer,Set(propertyId, value),SetName(propertyId, name),Remove(propertyId), andGetNamedProperties()(name → value, joining the dictionary with the values). - OlePropertyValue - one typed value, an immutable record:
Type(OlePropertyType),IsVector(the value is anobject[]of elements), and the boxed CLRValue. FactoriesCreate(int),Create(short),Create(long),Create(double),Create(bool),Create(string, type)(AnsiStringby default; passUnicodeStringfor UTF-16),Create(DateTimeOffset)(a FILETIME), andCreateBlob(byte[]). Accessors that returnnullon a type mismatch:AsString(),AsInt32(),AsInt64(),AsBoolean(),AsDateTimeOffset(),AsTimeSpan()(a FILETIME read as a duration, as the total-edit-time property uses it),AsBytes(), and the genericGetValueOrDefault<T>(). - OlePropertyType - the
VT_*codes:Empty,Null,Int16,Int32,Float32,Float64,Currency,Date,BinaryString,Boolean,Variant,Int8,UInt8,UInt16,UInt32,Int64,UInt64,AnsiString,UnicodeString,FileTime,Blob,ClipboardData.
Typed views
- SummaryInformation - the
\x05SummaryInformationset (StreamName).Read(Stream)ornew SummaryInformation(propertySet);PropertySetexposes the raw set; the nullable fieldsTitle,Subject,Author,Keywords,Comments,Template,LastAuthor,RevisionNumber,TotalEditTime,LastPrinted,CreateTime,LastSaveTime,PageCount,WordCount,CharacterCount,ApplicationName,Security. On aCompoundFile,TryGetSummaryInformation/SetSummaryInformationread and stage it at the root. - DocumentSummaryInformation - the
\x05DocumentSummaryInformationset (StreamName).Read(Stream)or the constructor;Category,PresentationTarget,Bytes,LineCount,ParagraphCount,SlideCount,NoteCount,HiddenCount,MultimediaClipCount,ScaleCrop,Manager,Company,LinksUpToDate; andCustomProperties- the user-defined second section keyed by name, empty when absent.TryGetDocumentSummaryInformation/SetDocumentSummaryInformationare theCompoundFilecounterparts.
Builders
- SummaryInformationBuilder - settable
CodePage(1252 by default),Title,Subject,Author,Keywords,Comments,LastAuthor,RevisionNumber,ApplicationName,CreateTime,LastSaveTime,PageCount,WordCount,CharacterCount; only assigned properties are written.ToPropertySet(),ToArray(),WriteTo(Stream). - DocumentSummaryInformationBuilder -
CodePage,Category,Manager,Company,LineCount,ParagraphCount,SlideCount, plusAddCustomProperty(name, value)for the user-defined section.ToPropertySet(),ToArray(),WriteTo(Stream).
Example
using Bodu.IO.Compound;
using Bodu.IO.Compound.PropertySets;
// Read: the typed view, then drop to the raw set for a property it does not surface.
using CompoundFile file = CompoundFile.OpenRead("report.doc");
if (file.TryGetDocumentSummaryInformation(out DocumentSummaryInformation? info))
{
Console.WriteLine($"{info.Company} / {info.Category}");
foreach (KeyValuePair<string, OlePropertyValue> custom in info.CustomProperties)
Console.WriteLine($"{custom.Key} = {custom.Value.Value} ({custom.Value.Type})");
OlePropertySet raw = info.PropertySet;
Console.WriteLine($"{raw.Sections.Count} section(s), code page {raw.CodePage}");
}
using Bodu.IO.Compound;
using Bodu.IO.Compound.PropertySets;
// Write: the standard set through a builder, and a custom set through the raw model.
var summary = new SummaryInformationBuilder
{
Title = "Quarterly report",
Author = "Ada",
CreateTime = DateTimeOffset.UtcNow,
};
var formatId = new Guid("6c1f0b1e-2f43-4a89-9d2e-0f5b8a7c3d10"); // your application's FMTID
var custom = new OlePropertySet(formatId, classId: Guid.Empty, codePage: 1252);
var section = new OlePropertySection(formatId, codePage: 1252);
section.Set(2, OlePropertyValue.Create("Northwind", OlePropertyType.UnicodeString));
section.Set(3, OlePropertyValue.Create(42));
section.Set(4, OlePropertyValue.CreateBlob(new byte[] { 1, 2, 3 }));
section.SetName(2, "Tenant");
custom.AddSection(section);
using (CompoundFile file = CompoundFile.Create("report.cfb"))
{
file.SetSummaryInformation(new SummaryInformation(summary.ToPropertySet()));
file.RootStorage.WritePropertySet("AppProperties", custom);
file.Commit();
}
// Round-trip the custom set from any storage.
using CompoundFile reread = CompoundFile.OpenRead("report.cfb");
if (reread.RootStorage.TryOpenPropertySet("AppProperties", out OlePropertySet? back))
Console.WriteLine(back.Sections[0].GetNamedProperties()["Tenant"].AsString()); // "Northwind"
Notes
- Typed versus custom. The two typed views cover the standard PIDs of the two Office metadata sets and expose
PropertySetfor everything else;DocumentSummaryInformation.CustomPropertiesis the convenient view of the user-defined second section. For a non-standard set - any FMTID, any stream name - use the raw model withCompoundStorage.TryOpenPropertySet/WritePropertySet. - First-section lookups.
OlePropertySet.TryGetValueand its indexer consult only the first section; address a second section throughSections[1]. - Strings and code pages.
Create(string)produces anAnsiStringencoded with the section'sCodePage(1252 by default on the builders); passOlePropertyType.UnicodeStringto store UTF-16 instead. - FILETIME is dual-natured. A
FileTimevalue is stored as its raw 100-nanosecond tick count, so it reads as a point in time (AsDateTimeOffset) or an elapsed duration (AsTimeSpan);Create(DateTimeOffset)writes the former. - Vectors round-trip by value.
IsVectorvalues surface asobject[]. A variant vector's elements re-emit with a type word inferred from each element's CLR value, so the guarantee for variant elements is value identity rather than byte identity. - Errors. A malformed set throws CompoundFileFormatException from
Parse/Readand fromTryOpenPropertySetwhen a stream by that name exists but does not parse;nullarguments throw ArgumentNullException. - See also: the property-sets guide, the authoring guide, and the container namespace Bodu.IO.Compound.
Classes
- DocumentSummaryInformation
Provides a strongly-typed view over the document-summary-information property set (
\x05DocumentSummaryInformation) of a compound file, including any user-defined custom properties.
- DocumentSummaryInformationBuilder
Authors a document-summary-information property set, producing an OlePropertySet that can be embedded as the
\x05DocumentSummaryInformationstream of a compound file, including an optional user-defined section of custom named properties.
- OlePropertySection
Represents a single section of an OLE property set - a format-identified group of properties keyed by property identifier (PID).
- OlePropertySet
Represents a parsed OLE property set - the managed counterpart of the COM
IPropertyStorageinterface - exposing its sections and the typed values they contain.
- OlePropertyValue
Represents a single typed value read from an OLE property set, the managed counterpart of a COM
PROPVARIANT.
- SummaryInformation
Provides a strongly-typed view over the standard summary-information property set (
\x05SummaryInformation) of a compound file.
- SummaryInformationBuilder
Authors a standard summary-information property set, producing an OlePropertySet that can be embedded as the
\x05SummaryInformationstream of a compound file.
Enums
- OlePropertyType
Identifies the scalar value type carried by an OlePropertyValue, corresponding to the COM
VARENUM(VT_*) constants used in OLE property sets.