Skip to content

Commit

Permalink
Port #190 from 2.4
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-visionaid committed Nov 16, 2024
1 parent 30e6daa commit 162d227
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
82 changes: 82 additions & 0 deletions OpenMcdf.Ole.Tests/OlePropertiesExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,88 @@ public void AddDocumentSummaryInformationCustomInfo()
}
}

/// As Test_DOCUMENT_SUMMARY_INFO_ADD_CUSTOM, but adding user defined properties with the AddUserDefinedProperty function
[TestMethod]
public void TestAddUserDefinedProperty()
{
using MemoryStream modifiedStream = new();
using (FileStream stream = File.OpenRead("english.presets.doc"))
stream.CopyTo(modifiedStream);

// Test value for a VT_FILETIME property
DateTime testNow = DateTime.UtcNow;

// english.presets.doc has a user defined property section, but no properties other than the codepage
using (var cf = RootStorage.Open(modifiedStream, StorageModeFlags.LeaveOpen))
{
CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation");
OlePropertiesContainer co = new(dsiStream);
OlePropertiesContainer userProperties = co.UserDefinedProperties!;
userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "StringProperty").Value = "Hello";
userProperties.AddUserDefinedProperty(VTPropertyType.VT_BOOL, "BooleanProperty").Value = true;
userProperties.AddUserDefinedProperty(VTPropertyType.VT_I4, "IntegerProperty").Value = 3456;
userProperties.AddUserDefinedProperty(VTPropertyType.VT_FILETIME, "DateProperty").Value = testNow;
userProperties.AddUserDefinedProperty(VTPropertyType.VT_R8, "DoubleProperty").Value = 1.234567d;

co.Save(dsiStream);
}

ValidateAddedUserDefinedProperties(modifiedStream, testNow);
}

// Validate that the user defined properties added by Test_DOCUMENT_SUMMARY_INFO_ADD_CUSTOM / Test_Add_User_Defined_Property are as expected
private static void ValidateAddedUserDefinedProperties(MemoryStream stream, DateTime testFileTimeValue)
{
using var cf = RootStorage.Open(stream);
using CfbStream cfbStream = cf.OpenStream("\u0005DocumentSummaryInformation");
OlePropertiesContainer co = new(cfbStream);
IList<OleProperty> propArray = co.UserDefinedProperties!.Properties;
Assert.AreEqual(6, propArray.Count);

// CodePage prop
Assert.AreEqual(1u, propArray[0].PropertyIdentifier);
Assert.AreEqual("0x00000001", propArray[0].PropertyName);
Assert.AreEqual((short)-535, propArray[0].Value);

// User properties
Assert.AreEqual("StringProperty", propArray[1].PropertyName);
Assert.AreEqual("Hello", propArray[1].Value);
Assert.AreEqual(VTPropertyType.VT_LPSTR, propArray[1].VTType);
Assert.AreEqual("BooleanProperty", propArray[2].PropertyName);
Assert.AreEqual(true, propArray[2].Value);
Assert.AreEqual(VTPropertyType.VT_BOOL, propArray[2].VTType);
Assert.AreEqual("IntegerProperty", propArray[3].PropertyName);
Assert.AreEqual(3456, propArray[3].Value);
Assert.AreEqual(VTPropertyType.VT_I4, propArray[3].VTType);
Assert.AreEqual("DateProperty", propArray[4].PropertyName);
Assert.AreEqual(testFileTimeValue, propArray[4].Value);
Assert.AreEqual(VTPropertyType.VT_FILETIME, propArray[4].VTType);
Assert.AreEqual("DoubleProperty", propArray[5].PropertyName);
Assert.AreEqual(1.234567d, propArray[5].Value);
Assert.AreEqual(VTPropertyType.VT_R8, propArray[5].VTType);
}

/// The names of user defined properties must be unique - adding a duplicate should throw.
[TestMethod]
public void TestAddUserDefinedPropertyShouldPreventDuplicates()
{
using MemoryStream modifiedStream = new();
using (FileStream stream = File.OpenRead("english.presets.doc"))
stream.CopyTo(modifiedStream);

using var cf = RootStorage.Open(modifiedStream);
CfbStream dsiStream = cf.OpenStream("\u0005DocumentSummaryInformation");
OlePropertiesContainer co = new(dsiStream);
OlePropertiesContainer userProperties = co.UserDefinedProperties!;

userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "StringProperty");

ArgumentException exception = Assert.ThrowsException<ArgumentException>(
() => userProperties.AddUserDefinedProperty(VTPropertyType.VT_LPSTR, "stringproperty"));

Assert.AreEqual("name", exception.ParamName);
}

// Try to read a document which contains Vector/String properties
// refs https://github.com/ironfede/openmcdf/issues/98
[TestMethod]
Expand Down
43 changes: 43 additions & 0 deletions OpenMcdf.Ole/OlePropertiesContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,49 @@ public OleProperty CreateProperty(VTPropertyType vtPropertyType, uint propertyId

public void Add(OleProperty property) => properties.Add(property);

/// <summary>
/// Create a new UserDefinedProperty.
/// </summary>
/// <param name="vtPropertyType">The type of property to create.</param>
/// <param name="name">The name of the new property.</param>
/// <returns>The new property.</returns>
/// <exception cref="InvalidOperationException">If UserDefinedProperties aren't allowed for this container.</exception>
/// <exception cref="ArgumentException">If a property with the name <paramref name="name"/> already exists."/></exception>
public OleProperty AddUserDefinedProperty(VTPropertyType vtPropertyType, string name)
{
if (this.ContainerType != ContainerType.UserDefinedProperties)
throw new InvalidOperationException();

// As per https://learn.microsoft.com/en-us/openspecs/windows_protocols/MS-OLEPS/4177a4bc-5547-49fe-a4d9-4767350fd9cf
// the property names have to be unique, and are case insensitive.
if (PropertyNames.Any(property => property.Value.Equals(name, StringComparison.InvariantCultureIgnoreCase)))

Check warning on line 145 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

Possible null reference argument for parameter 'source' in 'bool Enumerable.Any<KeyValuePair<uint, string>>(IEnumerable<KeyValuePair<uint, string>> source, Func<KeyValuePair<uint, string>, bool> predicate)'.

Check warning on line 145 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference argument for parameter 'source' in 'bool Enumerable.Any<KeyValuePair<uint, string>>(IEnumerable<KeyValuePair<uint, string>> source, Func<KeyValuePair<uint, string>, bool> predicate)'.

Check warning on line 145 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference argument for parameter 'source' in 'bool Enumerable.Any<KeyValuePair<uint, string>>(IEnumerable<KeyValuePair<uint, string>> source, Func<KeyValuePair<uint, string>, bool> predicate)'.

Check warning on line 145 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Possible null reference argument for parameter 'source' in 'bool Enumerable.Any<KeyValuePair<uint, string>>(IEnumerable<KeyValuePair<uint, string>> source, Func<KeyValuePair<uint, string>, bool> predicate)'.
{
throw new ArgumentException($"User defined property names must be unique and {name} already exists.", nameof(name));
}

// Work out a property identifier - must be > 1 and unique as per
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-oleps/333959a3-a999-4eca-8627-48a224e63e77
uint identifier = 2;

if (PropertyNames.Count > 0)

Check warning on line 154 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Debug)

Dereference of a possibly null reference.

Check warning on line 154 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Dereference of a possibly null reference.

Check warning on line 154 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Dereference of a possibly null reference.

Check warning on line 154 in OpenMcdf.Ole/OlePropertiesContainer.cs

View workflow job for this annotation

GitHub Actions / build (Release)

Dereference of a possibly null reference.
{
uint highestIdentifier = PropertyNames.Keys.Max();
identifier = Math.Max(highestIdentifier, 2) + 1;
}

PropertyNames[identifier] = name;

var op = new OleProperty(this)
{
VTType = vtPropertyType,
PropertyIdentifier = identifier
};

properties.Add(op);

return op;
}

public void RemoveProperty(uint propertyIdentifier)
{
OleProperty? toRemove = properties.FirstOrDefault(o => o.PropertyIdentifier == propertyIdentifier);
Expand Down

0 comments on commit 162d227

Please sign in to comment.