Table of Contents

Bodu.IO.Compound.PropertySets Namespace

Package

Bodu.IO.Compound

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

Key types

Raw model

  • OlePropertySet - the whole set. Read with Parse(ReadOnlyMemory<byte>) or Read(Stream) (both throw CompoundFileFormatException on a malformed set); author with new OlePropertySet(formatId, classId, codePage) and AddSection. 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), and GetNamedProperties() (name → value, joining the dictionary with the values).
  • OlePropertyValue - one typed value, an immutable record: Type (OlePropertyType), IsVector (the value is an object[] of elements), and the boxed CLR Value. Factories Create(int), Create(short), Create(long), Create(double), Create(bool), Create(string, type) (AnsiString by default; pass UnicodeString for UTF-16), Create(DateTimeOffset) (a FILETIME), and CreateBlob(byte[]). Accessors that return null on 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 generic GetValueOrDefault<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 \x05SummaryInformation set (StreamName). Read(Stream) or new SummaryInformation(propertySet); PropertySet exposes the raw set; the nullable fields Title, Subject, Author, Keywords, Comments, Template, LastAuthor, RevisionNumber, TotalEditTime, LastPrinted, CreateTime, LastSaveTime, PageCount, WordCount, CharacterCount, ApplicationName, Security. On a CompoundFile, TryGetSummaryInformation / SetSummaryInformation read and stage it at the root.
  • DocumentSummaryInformation - the \x05DocumentSummaryInformation set (StreamName). Read(Stream) or the constructor; Category, PresentationTarget, Bytes, LineCount, ParagraphCount, SlideCount, NoteCount, HiddenCount, MultimediaClipCount, ScaleCrop, Manager, Company, LinksUpToDate; and CustomProperties - the user-defined second section keyed by name, empty when absent. TryGetDocumentSummaryInformation / SetDocumentSummaryInformation are the CompoundFile counterparts.

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, plus AddCustomProperty(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 PropertySet for everything else; DocumentSummaryInformation.CustomProperties is the convenient view of the user-defined second section. For a non-standard set - any FMTID, any stream name - use the raw model with CompoundStorage.TryOpenPropertySet / WritePropertySet.
  • First-section lookups. OlePropertySet.TryGetValue and its indexer consult only the first section; address a second section through Sections[1].
  • Strings and code pages. Create(string) produces an AnsiString encoded with the section's CodePage (1252 by default on the builders); pass OlePropertyType.UnicodeString to store UTF-16 instead.
  • FILETIME is dual-natured. A FileTime value 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. IsVector values surface as object[]. 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 / Read and from TryOpenPropertySet when a stream by that name exists but does not parse; null arguments 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 \x05DocumentSummaryInformation stream 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 IPropertyStorage interface - 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 \x05SummaryInformation stream 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.