improve support for OLE2 compound document base format and some subformats

This commit is contained in:
Michael Becker 2022-08-06 19:13:23 -04:00
parent b5a0487158
commit 04a46da177
No known key found for this signature in database
GPG Key ID: DA394832305DA332
21 changed files with 1654 additions and 198 deletions

View File

@ -32,8 +32,8 @@ namespace UniversalEditor.Plugins.Genealogy.DataFormats.FamilyTreeMaker.Windows
CompoundDocumentObjectModel fsom = (objectModels.Pop () as CompoundDocumentObjectModel);
File IND_DB = fsom.Files["IND.DB"];
File INDGROUPS = fsom.Files["QEDIT0.DB"];
File IND_DB = fsom.Folders["Root Entry"].Files["IND.DB"];
File INDGROUPS = fsom.Folders["Root Entry"].Files["QEDIT0.DB"];
if (IND_DB == null)
throw new InvalidDataFormatException("IND.DB not found");

View File

@ -20,6 +20,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System.Collections.Generic;
using MBS.Framework;
using MBS.Framework.Settings;
using UniversalEditor.DataFormats.CompoundDocument;
using UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument;
using UniversalEditor.ObjectModels.CompoundDocument;
@ -38,7 +40,7 @@ namespace UniversalEditor.DataFormats.PropertyList.Microsoft.VisualStudio
{
if (_dfr == null)
{
_dfr = new DataFormatReference(GetType());
_dfr = new DataFormatReference(GetType(), base.MakeReferenceInternal());
_dfr.Capabilities.Add(typeof(PropertyListObjectModel), DataFormatCapabilities.All);
}
return _dfr;
@ -55,8 +57,7 @@ namespace UniversalEditor.DataFormats.PropertyList.Microsoft.VisualStudio
CompoundDocumentObjectModel fsom = (objectModels.Pop() as CompoundDocumentObjectModel);
PropertyListObjectModel plom = (objectModels.Pop() as PropertyListObjectModel);
for (int i = 0; i < fsom.Files.Count; i++ )
fsom.Files[i].Save(@"C:\Temp\SUO\" + fsom.Files[i].Name);
}
protected override void BeforeSaveInternal(Stack<ObjectModel> objectModels)
{

View File

@ -1,6 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<UniversalEditor Version="4.0">
<Associations>
<Association>
<!-- This association exists only to allow us to choose this DataFormat to view its structure as a FileSystemObjectModel. -->
<Filters>
<Filter Title="Microsoft OLE2 Compound Document file system">
</Filter>
</Filters>
<ObjectModels>
<ObjectModel TypeName="UniversalEditor.ObjectModels.FileSystem.FileSystemObjectModel" />
</ObjectModels>
<DataFormats>
<DataFormat TypeName="UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument.CompoundDocumentBaseDataFormat" />
</DataFormats>
</Association>
<Association>
<!-- This association exists only to allow us to choose this DataFormat to view its structure as a FileSystemObjectModel. -->
<Filters>
@ -8,11 +21,22 @@
</Filter>
</Filters>
<ObjectModels>
<ObjectModel TypeName="UniversalEditor.ObjectModels.FileSystem.FileSystemObjectModel" />
<ObjectModel TypeName="UniversalEditor.ObjectModels.CompoundDocument.CompoundDocumentObjectModel" />
</ObjectModels>
<DataFormats>
<DataFormat TypeName="UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument.CompoundDocumentDataFormat" />
<DataFormat TypeName="UniversalEditor.DataFormats.CompoundDocument.CompoundDocumentDataFormat" />
</DataFormats>
</Association>
<Association>
<Filters>
<Filter Title="Microsoft OLE2 SummaryInformation">
</Filter>
</Filters>
<ObjectModels>
<ObjectModel TypeName="UniversalEditor.ObjectModels.PropertyList.PropertyListObjectModel" />
</ObjectModels>
<DataFormats>
<DataFormat TypeName="UniversalEditor.DataFormats.CompoundDocument.SummaryInformation.SummaryInformationDataFormat" />
</DataFormats>
</Associations>
</UniversalEditor>

View File

@ -5,6 +5,9 @@
<!-- This association exists only to allow us to choose this DataFormat to view its structure as a FileSystemObjectModel. -->
<Filters>
<Filter Title="Microsoft Word 97-2003 Document">
<FileNameFilters>
<FileNameFilter>*.doc</FileNameFilter>
</FileNameFilters>
</Filter>
</Filters>
<ObjectModels>

View File

@ -19,12 +19,118 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using MBS.Framework;
using MBS.Framework.Settings;
using UniversalEditor.Accessors;
using UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.CompoundDocument;
using UniversalEditor.ObjectModels.FileSystem;
using UniversalEditor.ObjectModels.PropertyList;
namespace UniversalEditor.DataFormats.CompoundDocument
{
public class CompoundDocumentDataFormat : CompoundDocumentBaseDataFormat
{
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = new DataFormatReference(GetType());
SettingsGroup sgSummaryInfo = new SettingsGroup(new string[] { "General", "Summary" }, new Setting[]
{
new TextSetting("Title", "_Title"),
new TextSetting("Subject", "_Subject"),
new TextSetting("Author", "_Author"),
new TextSetting("Keywords", "_Keywords"),
new TextSetting("Comments", "_Comments"),
new TextSetting("AppName", "App name")
});
_dfr.ExportOptions.SettingsGroups.Add(sgSummaryInfo);
}
return _dfr;
}
protected override void AfterLoadInternal(Stack<ObjectModel> objectModels)
{
base.AfterLoadInternal(objectModels);
CompoundDocumentObjectModel cdom = (objectModels.Pop() as CompoundDocumentObjectModel);
File _CompObj = cdom.Folders["Root Entry"].Files["\u0001CompObj"];
if (_CompObj != null)
{
byte[] _CompObj_data = _CompObj.GetData();
MemoryAccessor ma = new MemoryAccessor(_CompObj_data);
int reserved1 = ma.Reader.ReadInt32();
int version = ma.Reader.ReadInt32();
byte[] reserved2 = ma.Reader.ReadBytes(20);
cdom.UserType = ReadString32(ma.Reader);
object ansiClipboardFormat = ReadClipboardFormatOrAnsiString(ma.Reader);
if (ansiClipboardFormat is uint)
{
cdom.ClipboardFormat = CompoundDocumentClipboardFormat.FromStandard((uint)ansiClipboardFormat);
}
else if (ansiClipboardFormat is string)
{
cdom.ClipboardFormat = CompoundDocumentClipboardFormat.FromString((string)ansiClipboardFormat);
}
cdom.AssociationTypeId = ReadString32(ma.Reader);
uint unicodeMarker = ma.Reader.ReadUInt32();
if (unicodeMarker != 0x71B239F4)
{
}
}
else
{
throw new InvalidDataFormatException();
}
File _SummaryInformation = cdom.Folders["Root Entry"].Files["\u0005SummaryInformation"];
if (_SummaryInformation != null)
{
byte[] data = _SummaryInformation.GetData();
MemoryAccessor ma = new MemoryAccessor(data);
PropertyListObjectModel plom = new PropertyListObjectModel();
Document.Load(plom, new SummaryInformation.SummaryInformationDataFormat(), ma);
}
// we need to remember to push it back onto the stack
objectModels.Push(cdom);
}
private object ReadClipboardFormatOrAnsiString(Reader reader)
{
uint markerOrLength = reader.ReadUInt32();
if (markerOrLength == 0)
{
return null;
}
else if (markerOrLength == 0xFFFFFFFF || markerOrLength == 0xFFFFFFFE)
{
uint format = reader.ReadUInt32();
return format;
}
else
{
return reader.ReadFixedLengthString(markerOrLength).TrimNull();
}
}
private string ReadString32(Reader reader)
{
uint length = reader.ReadUInt32();
string value = reader.ReadFixedLengthString(length);
return value.TrimNull();
}
}
}

View File

@ -0,0 +1,29 @@
//
// PropertyInfo.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation
{
public struct PropertyInfo
{
public uint Identifier { get; set; }
public uint Offset { get; set; }
}
}

View File

@ -0,0 +1,29 @@
//
// CompoundDocumentPropertySetInfo.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation
{
public struct PropertySetInfo
{
public Guid Guid { get; set; }
public uint Offset { get; set; }
}
}

View File

@ -0,0 +1,169 @@
//
// PropertySetPropertyType.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation
{
public enum PropertySetPropertyType : ushort
{
/// <summary>
/// Type is undefined, and the minimum property set version is 0.
/// </summary>
Empty = 0x0000,
/// <summary>
/// Type is null, and the minimum property set version is 0.
/// </summary>
Null = 0x0001,
/// <summary>
/// Type is 16-bit signed integer, and the minimum property set version is 0.
/// </summary>
I2 = 0x0002,
/// <summary>
/// Type is 32-bit signed integer, and the minimum property set version is 0.
/// </summary>
I4 = 0x0003,
/// <summary>
/// Type is 4-byte (single-precision) IEEE floating-point number, and the
/// minimum property set version is 0.
/// </summary>
R4 = 0x0004,
/// <summary>
/// Type is 8-byte (double-precision) IEEE floating-point number, and the
/// minimum property set version is 0.
/// </summary>
R8 = 0x0005,
/// <summary>
/// Type is CURRENCY, and the minimum property set version is 0.
/// </summary>
Currency = 0x0006,
/// <summary>
/// Type is DATE (OLE Automation), and the minimum property set version is 0.
/// </summary>
Date = 0x0007,
/// <summary>
/// Type is CodePageString, and the minimum property set version is 0.
/// </summary>
BStr = 0x0008,
/// <summary>
/// Type is HRESULT, and the minimum property set version is 0.
/// </summary>
Error = 0x000A,
/// <summary>
/// Type is VARIANT_BOOL, and the minimum property set version is 0.
/// </summary>
Bool = 0x000B,
/// <summary>
/// Type is DECIMAL, and the minimum property set version is 0.
/// </summary>
Decimal = 0x000E,
/// <summary>
/// Type is 1-byte signed integer, and the minimum property set version is 1.
/// </summary>
I1 = 0x0010,
/// <summary>
/// Type is 1-byte unsigned integer, and the minimum property set version is 0.
/// </summary>
UI1 = 0x0011,
/// <summary>
/// Type is 2-byte unsigned integer, and the minimum property set version is 0.
/// </summary>
UI2 = 0x0012,
/// <summary>
/// Type is 4-byte unsigned integer, and the minimum property set version is 0.
/// </summary>
UI4 = 0x0013,
/// <summary>
/// Type is 8-byte signed integer, and the minimum property set version is 0.
/// </summary>
I8 = 0x0014,
/// <summary>
/// Type is 8-byte unsigned integer, and the minimum property set version is 0.
/// </summary>
UI8 = 0x0015,
/// <summary>
/// Type is 4-byte signed integer, and the minimum property set version is 1.
/// </summary>
Int = 0x0016,
/// <summary>
/// Type is 4-byte unsigned integer, and the minimum property set version is 1.
/// </summary>
UInt = 0x0017,
/// <summary>
/// Type is CodePageString, and the minimum property set version is 0.
/// </summary>
LPStr = 0x001E,
/// <summary>
/// Type is UnicodeString, and the minimum property set version is 0.
/// </summary>
LPWStr = 0x001F,
/// <summary>
/// Type is FILETIME, and the minimum property set version is 0.
/// </summary>
FileTime = 0x0040,
/// <summary>
/// Type is binary large object (BLOB), and the minimum property set version is 0.
/// </summary>
Blob = 0x0041,
/// <summary>
/// Type is Stream, and the minimum property set version is 0. VT_STREAM is not
/// allowed in a simple property set.
/// </summary>
Stream = 0x0042,
/// <summary>
/// Type is Storage, and the minimum property set version is 0. VT_STORAGE is not
/// allowed in a simple property set.
/// </summary>
Storage = 0x0043,
/// <summary>
/// Type is Stream representing an Object in an application-specific manner, and the
/// minimum property set version is 0. VT_STREAMED_Object is not allowed in a simple
/// property set.
/// </summary>
StreamedObject = 0x0044,
/// <summary>
/// Type is Storage representing an Object in an application-specific manner, and the
/// minimum property set version is 0. VT_STORED_Object is not allowed in a simple
/// property set.
/// </summary>
StoredObject = 0x0045,
/// <summary>
/// Type is BLOB representing an object in an application-specific manner. The minimum
/// property set version is 0.
/// </summary>
BlobObject = 0x0046,
/// <summary>
/// Type is PropertyIdentifier, and the minimum property set version is 0.
/// </summary>
PropertyIdentifier = 0x0047,
/// <summary>
/// Type is CLSID, and the minimum property set version is 0.
/// </summary>
CLSID = 0x0048,
/// <summary>
/// Type is Stream with application-specific version GUID (VersionedStream). The
/// minimum property set version is 0. VT_VERSIONED_STREAM is not allowed in a
/// simple property set.
/// </summary>
VersionedStream = 0x0049,
Vector = 0x1000,
Array = 0x2000
}
}

View File

@ -0,0 +1,560 @@
//
// SummaryInformationDataFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
using MBS.Framework;
using UniversalEditor.IO;
using UniversalEditor.ObjectModels.PropertyList;
namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation
{
public class SummaryInformationDataFormat : DataFormat
{
private static DataFormatReference _dfr = null;
protected override DataFormatReference MakeReferenceInternal()
{
if (_dfr == null)
{
_dfr = base.MakeReferenceInternal();
_dfr.Capabilities.Add(typeof(PropertyListObjectModel), DataFormatCapabilities.All);
}
return _dfr;
}
public Endianness Endianness { get; set; } = Endianness.LittleEndian;
public ushort FormatVersion { get; set; } = 0;
public uint SystemIdentifier { get; set; } = 0x00020006;
public Guid ClassIdentifier { get; set; } = Guid.Empty;
protected override void LoadInternal(ref ObjectModel objectModel)
{
PropertyListObjectModel plom = (objectModel as PropertyListObjectModel);
if (plom == null)
{
throw new ObjectModelNotSupportedException();
}
Reader reader = Accessor.Reader;
ushort byteOrder = reader.ReadUInt16();
if (byteOrder == 0xFFFE)
{
Endianness = Endianness.LittleEndian;
}
else if (byteOrder == 0xFEFF)
{
Endianness = Endianness.BigEndian;
}
else
{
throw new InvalidDataFormatException("file must begin with a valid big-endian or little-endian byte order mark (BOM)");
}
FormatVersion = reader.ReadUInt16();
if (FormatVersion != 0)
{
throw new InvalidDataFormatException(String.Format("unsupported format version {0}", FormatVersion));
}
SystemIdentifier = reader.ReadUInt32();
ClassIdentifier = reader.ReadGuid();
uint propertySetCount = reader.ReadUInt32();
PropertySetInfo[] propertySetInfos = new PropertySetInfo[propertySetCount];
Group[] propertySetGroups = new Group[propertySetCount];
for (uint i = 0; i < propertySetCount; i++)
{
PropertySetInfo propertySetInfo = new PropertySetInfo();
propertySetInfo.Guid = reader.ReadGuid();
propertySetInfo.Offset = reader.ReadUInt32();
propertySetInfos[i] = propertySetInfo;
propertySetGroups[i] = new Group(propertySetInfo.Guid.ToString("B"));
propertySetGroups[i].SetExtraData<Guid>("guid", propertySetInfo.Guid);
plom.Items.Add(propertySetGroups[i]);
}
for (int i = 0; i < propertySetInfos.Length; i++)
{
PropertySetInfo propertySetInfo = propertySetInfos[i];
// we should be at offset 48, if propertySetCount == 1
uint propertySetSize = reader.ReadUInt32();
uint propertySetPropertyCount = reader.ReadUInt32();
PropertyInfo[] pis = new PropertyInfo[propertySetPropertyCount];
for (uint j = 0; j < propertySetPropertyCount; j++)
{
PropertyInfo pi = new PropertyInfo();
pi.Identifier = reader.ReadUInt32();
pi.Offset = reader.ReadUInt32();
pis[j] = pi;
Property property = new Property(pi.Identifier.ToString());
property.SetExtraData<PropertyInfo>("info", pi);
propertySetGroups[i].Items.Add(property);
}
}
for (int i = 0; i < propertySetInfos.Length; i++)
{
IEnumerable<Property> properties = propertySetGroups[i].Items.OfType<Property>();
foreach (Property property in properties)
{
PropertyInfo pi = property.GetExtraData<PropertyInfo>("info");
Accessor.Seek(propertySetInfos[i].Offset + pi.Offset, SeekOrigin.Begin);
object value = ReadTypedValue(Accessor.Reader);
property.Value = value;
}
}
}
public static object ReadTypedValue(Reader reader)
{
PropertySetPropertyType propertyType = (PropertySetPropertyType)reader.ReadUInt16();
ushort padding = reader.ReadUInt16();
return ReadTypedValue(reader, propertyType);
}
public static object ReadTypedValue(Reader reader, PropertySetPropertyType propertyType)
{
object value = null;
switch (propertyType)
{
case PropertySetPropertyType.Empty:
{
break;
}
case PropertySetPropertyType.Null:
{
break;
}
case PropertySetPropertyType.I2:
{
value = reader.ReadInt16();
break;
}
case PropertySetPropertyType.I4:
{
value = reader.ReadInt32();
break;
}
case PropertySetPropertyType.R4:
{
value = reader.ReadSingle();
break;
}
case PropertySetPropertyType.R8:
{
value = reader.ReadDouble();
break;
}
case PropertySetPropertyType.Currency:
{
value = reader.ReadUInt64();
break;
}
case PropertySetPropertyType.Date:
{
value = DateTime.FromOADate(reader.ReadDouble());
break;
}
case PropertySetPropertyType.BStr:
{
value = ReadCodePageString(reader);
break;
}
case PropertySetPropertyType.Error:
{
value = reader.ReadUInt32();
break;
}
case PropertySetPropertyType.Bool:
{
value = reader.ReadBoolean();
break;
}
case PropertySetPropertyType.Decimal:
{
value = reader.ReadDecimal();
break;
}
case PropertySetPropertyType.I1:
{
value = reader.ReadSByte();
break;
}
case PropertySetPropertyType.UI1:
{
value = reader.ReadByte();
break;
}
case PropertySetPropertyType.UI2:
{
value = reader.ReadUInt16();
break;
}
case PropertySetPropertyType.UI4:
{
value = reader.ReadUInt32();
break;
}
case PropertySetPropertyType.I8:
{
value = reader.ReadInt64();
break;
}
case PropertySetPropertyType.UI8:
{
value = reader.ReadUInt64();
break;
}
case PropertySetPropertyType.Int:
{
value = reader.ReadInt32();
break;
}
case PropertySetPropertyType.UInt:
{
value = reader.ReadUInt32();
break;
}
case PropertySetPropertyType.LPStr:
{
value = ReadCodePageString(reader);
break;
}
case PropertySetPropertyType.LPWStr:
{
value = ReadUnicodeString(reader);
break;
}
case PropertySetPropertyType.FileTime:
{
uint lowDateTime = reader.ReadUInt32();
uint highDateTime = reader.ReadUInt32();
value = null;
break;
}
default:
{
break;
}
}
reader.Align(4);
return value;
}
public static void WriteTypedValue(Writer writer, object value)
{
if (value is byte)
{
WriteTypedValue(writer, value, PropertySetPropertyType.UI1);
}
else if (value is short)
{
WriteTypedValue(writer, value, PropertySetPropertyType.I2);
}
else if (value is int)
{
WriteTypedValue(writer, value, PropertySetPropertyType.I4);
}
else if (value is long)
{
WriteTypedValue(writer, value, PropertySetPropertyType.I8);
}
else if (value is sbyte)
{
WriteTypedValue(writer, value, PropertySetPropertyType.I1);
}
else if (value is ushort)
{
WriteTypedValue(writer, value, PropertySetPropertyType.UI2);
}
else if (value is uint)
{
WriteTypedValue(writer, value, PropertySetPropertyType.UI4);
}
else if (value is ulong)
{
WriteTypedValue(writer, value, PropertySetPropertyType.UI8);
}
else if (value is string)
{
WriteTypedValue(writer, value, PropertySetPropertyType.LPWStr);
}
else
{
}
}
public static void WriteTypedValue(Writer writer, object value, PropertySetPropertyType propertyType)
{
writer.WriteUInt32((uint)propertyType);
switch (propertyType)
{
case PropertySetPropertyType.Empty:
{
break;
}
case PropertySetPropertyType.Null:
{
break;
}
case PropertySetPropertyType.I2:
{
writer.WriteInt16((short)value);
break;
}
case PropertySetPropertyType.I4:
{
writer.WriteInt32((int)value);
break;
}
case PropertySetPropertyType.R4:
{
writer.WriteSingle((float)value);
break;
}
case PropertySetPropertyType.R8:
{
writer.WriteDouble((double)value);
break;
}
case PropertySetPropertyType.Currency:
{
writer.WriteUInt64((ulong)value);
break;
}
case PropertySetPropertyType.Date:
{
writer.WriteDouble(((DateTime)value).ToOADate());
break;
}
case PropertySetPropertyType.BStr:
{
WriteCodePageString(writer, (string)value);
break;
}
case PropertySetPropertyType.Error:
{
writer.WriteUInt32((uint)value);
break;
}
case PropertySetPropertyType.Bool:
{
writer.WriteBoolean((bool)value);
break;
}
case PropertySetPropertyType.Decimal:
{
writer.WriteDecimal((decimal)value);
break;
}
case PropertySetPropertyType.I1:
{
writer.WriteSByte((sbyte)value);
break;
}
case PropertySetPropertyType.UI1:
{
writer.WriteByte((byte)value);
break;
}
case PropertySetPropertyType.UI2:
{
writer.WriteUInt16((ushort)value);
break;
}
case PropertySetPropertyType.UI4:
{
writer.WriteUInt32((uint)value);
break;
}
case PropertySetPropertyType.I8:
{
writer.WriteInt64((long)value);
break;
}
case PropertySetPropertyType.UI8:
{
writer.WriteUInt64((ulong)value);
break;
}
case PropertySetPropertyType.Int:
{
writer.WriteInt32((int)value);
break;
}
case PropertySetPropertyType.UInt:
{
writer.WriteUInt32((uint)value);
break;
}
case PropertySetPropertyType.LPStr:
{
WriteCodePageString(writer, (string)value);
break;
}
case PropertySetPropertyType.LPWStr:
{
WriteUnicodeString(writer, (string)value);
break;
}
case PropertySetPropertyType.FileTime:
{
uint lowDateTime = 0;
uint highDateTime = 0;
writer.WriteUInt32(lowDateTime);
writer.WriteUInt32(highDateTime);
break;
}
default:
{
break;
}
}
writer.Align(4);
}
public static string ReadUnicodeString(Reader reader)
{
uint length = reader.ReadUInt32();
return reader.ReadFixedLengthString(length * 2, Encoding.UTF16LittleEndian);
}
public static void WriteUnicodeString(Writer writer, string value)
{
uint length = (uint)value.Length;
writer.WriteUInt32(length);
writer.WriteFixedLengthString(value, Encoding.UTF16LittleEndian);
}
public static string ReadCodePageString(Reader reader)
{
uint length = reader.ReadUInt32();
return reader.ReadFixedLengthString(length);
}
public static void WriteCodePageString(Writer writer, string value)
{
uint length = (uint)value.Length;
writer.WriteUInt32(length);
writer.WriteFixedLengthString(value);
}
protected override void SaveInternal(ObjectModel objectModel)
{
PropertyListObjectModel plom = (objectModel as PropertyListObjectModel);
if (plom == null)
{
throw new ObjectModelNotSupportedException();
}
switch (Endianness)
{
case Endianness.LittleEndian:
{
Accessor.Writer.WriteUInt16(0xFFFE);
break;
}
case Endianness.BigEndian:
{
Accessor.Writer.WriteUInt16(0xFEFF);
break;
}
default:
{
throw new NotSupportedException();
}
}
Accessor.Writer.WriteUInt16(FormatVersion);
Accessor.Writer.WriteUInt32(SystemIdentifier);
Accessor.Writer.WriteGuid(ClassIdentifier);
IEnumerable<Group> groups = plom.Items.OfType<Group>();
Accessor.Writer.WriteUInt32((uint)groups.Count());
uint offset = (uint)(28 + (20 * groups.Count()));
foreach (Group group in groups)
{
Accessor.Writer.WriteGuid(group.GetExtraData<Guid>("guid"));
Accessor.Writer.WriteUInt32(offset);
IEnumerable<Property> properties = group.Items.OfType<Property>();
offset += (uint)(8 + (8 * properties.Count()));
}
offset -= (uint) Accessor.Position;
foreach (Group group in groups)
{
IEnumerable<Property> properties = group.Items.OfType<Property>();
uint propertySetSize = (uint)(8 + (8 * properties.Count()));
foreach (Property property in properties)
{
propertySetSize += CalculatePropertySize(property);
}
Accessor.Writer.WriteUInt32(propertySetSize);
Accessor.Writer.WriteUInt32((uint)properties.Count());
foreach (Property property in properties)
{
uint identifier = 0;
if (!UInt32.TryParse(property.Name, out identifier))
{
identifier = 0;
}
Accessor.Writer.WriteUInt32(identifier);
Accessor.Writer.WriteUInt32(offset);
offset += CalculatePropertySize(property);
}
}
foreach (Group group in groups)
{
IEnumerable<Property> properties = group.Items.OfType<Property>();
foreach (Property property in properties)
{
WriteTypedValue(Accessor.Writer, property.Value);
}
}
}
private uint CalculatePropertySize(Property property)
{
uint size = 4;
if (property.Value != null)
{
if (property.Value is string)
{
size += (uint)(4 + ((string)property.Value).Length).Align(4);
}
size += (uint)property.Value.SizeOf().Align(4);
}
return size;
}
}
}

View File

@ -41,77 +41,32 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
_dfr = new DataFormatReference(GetType());
_dfr.Capabilities.Add(typeof(FileSystemObjectModel), DataFormatCapabilities.All);
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(SectorSize), "_Sector size (in bytes)", 512, 128));
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(ShortSectorSize), "S_hort sector size (in bytes)", 64));
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(MinimumStandardStreamSize), "_Minimum standard stream size (in bytes)", 4096, 4096));
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(SectorSize), "_Sector size (in bytes)", 512, 128));
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(ShortSectorSize), "S_hort sector size (in bytes)", 64));
_dfr.ExportOptions.SettingsGroups[0].Settings.Add(new RangeSetting(nameof(MinimumStandardStreamSize), "_Minimum standard stream size (in bytes)", 4096, 4096));
_dfr.Sources.Add("http://www.openoffice.org/sc/compdocfileformat.pdf");
}
return _dfr;
}
private Guid mvarUniqueIdentifier = Guid.Empty;
public Guid UniqueIdentifier { get { return mvarUniqueIdentifier; } set { mvarUniqueIdentifier = value; } }
private Version mvarFormatVersion = new Version(3, 62);
public Version FormatVersion { get { return mvarFormatVersion; } set { mvarFormatVersion = value; } }
private Endianness mvarEndianness = Endianness.LittleEndian;
public Endianness Endianness { get { return mvarEndianness; } set { mvarEndianness = value; } }
private uint mvarSectorSize = 512;
public uint SectorSize { get { return mvarSectorSize; } set { mvarSectorSize = value; } }
private uint mvarShortSectorSize = 64;
public uint ShortSectorSize { get { return mvarShortSectorSize; } set { mvarShortSectorSize = value; } }
/// <summary>
/// Total number of sectors used for the sector allocation table
/// </summary>
private uint mvarSectorAllocationTableSize = 0;
/// <summary>
/// Sector ID of the first sector of the directory stream
/// </summary>
private uint mvarDirectoryStreamFirstSectorID = 0;
private uint mvarMinimumStandardStreamSize = 4096;
/// <summary>
/// Minimum size of a standard stream (in bytes). Minimum allowed and most-used size is
/// 4096 bytes. Streams with an actual size smaller than (and not equal to) this value
/// are stored as short-streams.
/// </summary>
public uint MinimumStandardStreamSize { get { return mvarMinimumStandardStreamSize; } set { mvarMinimumStandardStreamSize = value; } }
/// <summary>
/// Sector ID of the first sector of the short-sector allocation table (or
/// <see cref="CompoundDocumentKnownSectorID.EndOfChain" /> if not extant).
/// </summary>
private int mvarShortSectorAllocationTableFirstSectorID = 0;
/// <summary>
/// Total number of sectors used for the short-sector allocation table.
/// </summary>
private int mvarShortSectorAllocationTableSize = 0;
/// <summary>
/// Sector ID of the first sector of the master sector allocation table (or
/// <see cref="CompoundDocumentKnownSectorID.EndOfChain" /> if no additional sectors
/// used).
/// </summary>
private int mvarMasterSectorAllocationTableFirstSectorID = 0;
/// <summary>
/// Total number of sectors used for the master sector allocation table.
/// </summary>
private int mvarMasterSectorAllocationTableSize = 0;
public Guid UniqueIdentifier { get; set; } = Guid.Empty;
public Version FormatVersion { get; set; } = new Version(3, 62);
public Endianness Endianness { get; set; } = Endianness.LittleEndian;
public uint SectorSize { get; set; } = 512;
public uint ShortSectorSize { get; set; } = 64;
public uint MinimumStandardStreamSize { get; set; } = 4096;
private int mvarShortSectorFirstSectorID = 0;
private int GetSectorPositionFromSectorID(int sectorID)
{
if (sectorID < 0) return 0;
return (int)(512 + (sectorID * mvarSectorSize));
return (int)(512 + (sectorID * SectorSize));
}
private int GetShortSectorPositionFromSectorID(int sectorID)
{
if (sectorID < 0) return 0;
return (int)(sectorID * mvarShortSectorSize);
return (int)(sectorID * ShortSectorSize);
}
private byte[] mvarShortSectorContainerStreamData = null;
@ -124,6 +79,8 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
private List<int> shortSectorAllocationTableSectors = new List<int>();
private int[] sectorAllocationTable = new int[0];
private Folder rootEntry = null;
protected override void LoadInternal(ref ObjectModel objectModel)
{
FileSystemObjectModel fsom = (objectModel as FileSystemObjectModel);
@ -131,65 +88,67 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
Reader reader = base.Accessor.Reader;
// The header is always located at the beginning of the file, and its size is
// exactly 512 bytes. This implies that the first sector (0) always starts at
// file offset 512.
byte[] signature = reader.ReadBytes(8);
if (!signature.Match(VALID_SIGNATURE))
CompoundDocumentHeader header = ReadCompoundDocumentHeader(reader);
if (!header.Signature.Match(VALID_SIGNATURE))
{
throw new InvalidDataFormatException("File does not begin with { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 }");
}
mvarUniqueIdentifier = reader.ReadGuid();
UniqueIdentifier = header.UniqueIdentifier;
ushort MinorVersion = reader.ReadUInt16();
ushort MajorVersion = reader.ReadUInt16();
mvarFormatVersion = new Version(MajorVersion, MinorVersion);
byte[] ByteOrderIdentifier = reader.ReadBytes(2);
if (ByteOrderIdentifier[0] == 0xFE && ByteOrderIdentifier[1] == 0xFF)
FormatVersion = new Version(header.MajorVersion, header.MinorVersion);
if (FormatVersion.Major == 0x0003 || FormatVersion.Major == 0x0004)
{
mvarEndianness = Endianness.LittleEndian;
if (FormatVersion.Minor != 0x003E)
{
// sanity check
}
}
else if (ByteOrderIdentifier[0] == 0xFF && ByteOrderIdentifier[1] == 0xFE)
if (header.ByteOrderIdentifier[0] == 0xFE && header.ByteOrderIdentifier[1] == 0xFF)
{
mvarEndianness = Endianness.BigEndian;
Endianness = Endianness.LittleEndian;
}
else if (header.ByteOrderIdentifier[0] == 0xFF && header.ByteOrderIdentifier[1] == 0xFE)
{
Endianness = Endianness.BigEndian;
}
else
{
throw new InvalidDataFormatException("Invalid value for byte order (" + ByteOrderIdentifier[0].ToString("X").PadLeft(2, '0') + ", " + ByteOrderIdentifier[1].ToString("X").PadLeft(2, '0') + ")");
throw new InvalidDataFormatException("Invalid value for byte order (" + header.ByteOrderIdentifier[0].ToString("X").PadLeft(2, '0') + ", " + header.ByteOrderIdentifier[1].ToString("X").PadLeft(2, '0') + ")");
}
ushort uSectorSize = reader.ReadUInt16();
mvarSectorSize = (uint)(Math.Pow(2, uSectorSize));
SectorSize = (uint)(Math.Pow(2, header.SectorSize));
ushort uShortSectorSize = reader.ReadUInt16();
mvarShortSectorSize = (uint)(Math.Pow(2, uShortSectorSize));
// If Major Version is 3, the Sector Shift MUST be 0x0009, specifying a sector size of 512 bytes.
// If Major Version is 4, the Sector Shift MUST be 0x000C, specifying a sector size of 4096 bytes.
ShortSectorSize = (uint)(Math.Pow(2, header.ShortSectorSize));
// This field MUST be set to 0x0006. This field specifies the sector size of
// the Mini Stream as a power of 2.The sector size of the Mini Stream MUST be 64 bytes.
if (ShortSectorSize > SectorSize) throw new InvalidDataFormatException("Short sector size (" + ShortSectorSize.ToString() + ") exceeds sector size (" + SectorSize.ToString() + ")");
byte[] unused1 = reader.ReadBytes(10);
if (FormatVersion.Major == 3)
{
if (header.DirectorySectorCount != 0)
{
// If Major Version is 3, the Number of Directory Sectors MUST be zero. This field is not
// supported for version 3 compound files.
}
}
mvarSectorAllocationTableSize = reader.ReadUInt32();
mvarDirectoryStreamFirstSectorID = reader.ReadUInt32();
uint unused2 = reader.ReadUInt32();
mvarMinimumStandardStreamSize = reader.ReadUInt32();
mvarShortSectorAllocationTableFirstSectorID = reader.ReadInt32();
mvarShortSectorAllocationTableSize = reader.ReadInt32();
// SecID of first sector of the master sector allocation table, or 2 (End Of Chain) if no additional sectors used
mvarMasterSectorAllocationTableFirstSectorID = reader.ReadInt32();
// Total number of sectors used for the master sector allocation table
mvarMasterSectorAllocationTableSize = reader.ReadInt32();
MinimumStandardStreamSize = header.MinimumStandardStreamSize;
#region Read Master Sector Allocation Table
// First part of the master sector allocation table, containing 109 SecIDs
int[] masterSectorAllocationTable = reader.ReadInt32Array(109);
// TODO: test this! when MSAT contains more than 109 SecIDs
int countForMSAT = (int)((double)mvarSectorSize / 4);
int nextSectorForMSAT = mvarMasterSectorAllocationTableFirstSectorID;
int nextPositionForMSAT = masterSectorAllocationTable.Length;
int countForMSAT = (int)((double)SectorSize / 4);
int nextSectorForMSAT = header.MasterSectorAllocationTableFirstSectorID;
int nextPositionForMSAT = header.MasterSectorAllocationTable.Length;
int[] masterSectorAllocationTable = header.MasterSectorAllocationTable;
while (nextSectorForMSAT != (int)CompoundDocumentKnownSectorID.EndOfChain)
{
@ -201,7 +160,7 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
}
#endregion
#region Read Sector Allocation Table
sectorAllocationTable = new int[(int)(mvarSectorSize / 4)];
sectorAllocationTable = new int[(int)(SectorSize / 4)];
for (int i = 0; i < masterSectorAllocationTable.Length; i++)
{
if (masterSectorAllocationTable[i] == -1)
@ -211,21 +170,21 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
// last SecID is the special End Of Chain SecID with the value 2( ➜ 3.1).
SeekToSector(masterSectorAllocationTable[i]);
int[] sectorAllocationTablePart = reader.ReadInt32Array((int)(mvarSectorSize / 4));
int[] sectorAllocationTablePart = reader.ReadInt32Array((int)(SectorSize / 4));
Array.Resize<int>(ref sectorAllocationTable, sectorAllocationTable.Length + sectorAllocationTablePart.Length);
Array.Copy(sectorAllocationTablePart, 0, sectorAllocationTable, (int)(i * (mvarSectorSize / 4)), sectorAllocationTablePart.Length);
Array.Copy(sectorAllocationTablePart, 0, sectorAllocationTable, (int)(i * (SectorSize / 4)), sectorAllocationTablePart.Length);
}
#endregion
// read directory entries - each entry is 128 bytes
byte[] directoryData = ReadDirectoryData(reader);
byte[] directoryData = ReadDirectoryData(reader, header);
#region Read Short Sector Allocation Table
shortSectorAllocationTableSectors = new List<int>();
if (mvarShortSectorAllocationTableFirstSectorID >= 0)
if (header.ShortSectorAllocationTableFirstSectorID >= 0)
{
int sector = mvarShortSectorAllocationTableFirstSectorID;
int sector = header.ShortSectorAllocationTableFirstSectorID;
while (sector != -2)
{
shortSectorAllocationTableSectors.Add(sector);
@ -240,61 +199,29 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
}
}
shortSectorAllocationTable = new int[(mvarSectorSize / 4) * shortSectorAllocationTableSectors.Count];
byte[] shortSectorAllocationTableData = new byte[mvarSectorSize * shortSectorAllocationTableSectors.Count];
shortSectorAllocationTable = new int[(SectorSize / 4) * shortSectorAllocationTableSectors.Count];
byte[] shortSectorAllocationTableData = new byte[SectorSize * shortSectorAllocationTableSectors.Count];
for (int i = 0; i < shortSectorAllocationTableSectors.Count; i++)
{
SeekToSector(shortSectorAllocationTableSectors[i]);
int[] tablePart = reader.ReadInt32Array((int)(mvarSectorSize / 4));
int[] tablePart = reader.ReadInt32Array((int)(SectorSize / 4));
Array.Copy(tablePart, 0, shortSectorAllocationTable, (i * tablePart.Length), tablePart.Length);
}
#endregion
#region Read Sector Directory Entries
Accessors.MemoryAccessor maDirectory = new Accessors.MemoryAccessor(directoryData);
Reader directoryReader = new Reader(maDirectory);
List<CompoundDocumentStorageHeader> listHeaders = new List<CompoundDocumentStorageHeader>();
while (!directoryReader.EndOfStream)
{
// The first directory entry always represents the root storage entry
byte[] storageNameBytes = directoryReader.ReadBytes(64);
ushort storageNameLength = directoryReader.ReadUInt16();
CompoundDocumentStorageHeader sh = ReadStorageHeader(directoryReader);
byte[] storageNameValidBytes = new byte[storageNameLength];
Array.Copy(storageNameBytes, 0, storageNameValidBytes, 0, storageNameValidBytes.Length);
string storageName = System.Text.Encoding.Unicode.GetString(storageNameValidBytes);
storageName = storageName.TrimNull();
// if (storageName.Length != storageNameLength) throw new InvalidDataFormatException("Sanity check: storage name length is not actual length of storage name");
CompoundDocumentStorageType storageType = (CompoundDocumentStorageType)directoryReader.ReadByte();
byte storageNodeColor = directoryReader.ReadByte();
int leftChildNodeDirectoryID = directoryReader.ReadInt32();
int rightChildNodeDirectoryID = directoryReader.ReadInt32();
// directory ID of the root node entry of the red-black tree of all members of the root storage
int rootNodeEntryDirectoryID = directoryReader.ReadInt32();
Guid uniqueIdentifier = directoryReader.ReadGuid();
uint flags = directoryReader.ReadUInt32();
long creationTimestamp = directoryReader.ReadInt64();
long lastModificationTimestamp = directoryReader.ReadInt64();
// SecID of first sector or short-sector, if this entry refers to a stream
// SecID of first sector of the short-stream container stream, if this is the root storage entry
// 0 otherwise
int firstSectorOfStream = directoryReader.ReadInt32();
// Total stream size in bytes, if this entry refers to a stream,
// total size of the short-stream container stream, if this is the root storage entry
// 0 otherwise
int streamLength = directoryReader.ReadInt32();
int unused3 = directoryReader.ReadInt32();
if (storageType == CompoundDocumentStorageType.RootStorage)
if (sh.StorageType == CompoundDocumentStorageType.RootStorage)
{
// this is the root storage entry
mvarShortSectorFirstSectorID = firstSectorOfStream;
mvarShortSectorFirstSectorID = sh.FirstSectorIndex;
#region Read Short Stream Container Stream
List<int> shortStreamContainerStreamSectors = new List<int>();
@ -313,42 +240,46 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
}
}
}
byte[] shortStreamContainerStreamData = new byte[shortStreamContainerStreamSectors.Count * mvarSectorSize];
byte[] shortStreamContainerStreamData = new byte[shortStreamContainerStreamSectors.Count * SectorSize];
int i = 0;
foreach (int sector in shortStreamContainerStreamSectors)
{
int wpos = GetSectorPositionFromSectorID(sector);
reader.Seek(wpos, SeekOrigin.Begin);
byte[] sectorData = reader.ReadBytes(mvarSectorSize);
byte[] sectorData = reader.ReadBytes(SectorSize);
Array.Copy(sectorData, 0, shortStreamContainerStreamData, i, sectorData.Length);
i += sectorData.Length;
}
mvarShortSectorContainerStreamData = shortStreamContainerStreamData;
#endregion
rootEntry = new Folder();
rootEntry.Name = sh.Name; // MUST be "Root Entry", according to spec, but we want to be versatile
fsom.Folders.Add(rootEntry);
}
else if (storageType == CompoundDocumentStorageType.UserStorage)
else if (sh.StorageType == CompoundDocumentStorageType.UserStorage)
{
}
else if (storageType == CompoundDocumentStorageType.UserStream)
else if (sh.StorageType == CompoundDocumentStorageType.UserStream)
{
File file = new File();
file.Name = storageName;
file.Size = streamLength;
file.Name = sh.Name;
file.Size = sh.Length;
file.Properties.Add("reader", reader);
file.Properties.Add("firstSector", firstSectorOfStream);
file.Properties.Add("length", streamLength);
file.Properties.Add("firstSector", sh.FirstSectorIndex);
file.Properties.Add("length", sh.Length);
file.DataRequest += file_DataRequest;
fsom.Files.Add(file);
rootEntry.Files.Add(file);
Console.WriteLine("{3} {0} {1} {2}", file.Name, firstSectorOfStream, streamLength, storageType.ToString());
Console.WriteLine("{3} {0} {1} {2}", file.Name, sh.FirstSectorIndex, sh.Length, sh.StorageType.ToString());
}
else if (storageType == CompoundDocumentStorageType.Empty)
else if (sh.StorageType == CompoundDocumentStorageType.Empty)
{
Console.WriteLine(storageName + " is empty; should we included it in file list?");
Console.WriteLine(String.Format("{0} is empty; should we included it in file list?", sh.Name));
}
else
{
throw new NotImplementedException(storageType.ToString() + " not implemented");
throw new NotImplementedException(String.Format("storage type {0} not implemented", sh.StorageType));
}
}
#endregion
@ -356,6 +287,131 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
WriteLog(fsom);
}
private CompoundDocumentHeader ReadCompoundDocumentHeader(Reader reader)
{
CompoundDocumentHeader header = new CompoundDocumentHeader();
// The header is always located at the beginning of the file, and its size is
// exactly 512 bytes. This implies that the first sector (0) always starts at
// file offset 512.
header.Signature = reader.ReadBytes(8);
header.UniqueIdentifier = reader.ReadGuid();
header.MinorVersion = reader.ReadUInt16();
header.MajorVersion = reader.ReadUInt16();
header.ByteOrderIdentifier = reader.ReadBytes(2);
header.SectorSize = reader.ReadUInt16();
header.ShortSectorSize = reader.ReadUInt16();
header.Unused1 = reader.ReadBytes(6);
header.DirectorySectorCount = reader.ReadUInt32();
header.SectorAllocationTableSize = reader.ReadUInt32();
header.DirectoryStreamFirstSectorID = reader.ReadUInt32();
header.TransactionSignatureNumber = reader.ReadUInt32();
header.MinimumStandardStreamSize = reader.ReadUInt32();
header.ShortSectorAllocationTableFirstSectorID = reader.ReadInt32();
header.ShortSectorAllocationTableSize = reader.ReadInt32();
// SecID of first sector of the master sector allocation table, or 2 (End Of Chain) if no additional sectors used
header.MasterSectorAllocationTableFirstSectorID = reader.ReadInt32();
// Total number of sectors used for the master sector allocation table
header.MasterSectorAllocationTableSize = reader.ReadInt32();
header.MasterSectorAllocationTable = reader.ReadInt32Array(109);
return header;
}
private CompoundDocumentStorageHeader ReadStorageHeader(Reader directoryReader)
{
CompoundDocumentStorageHeader sh = new CompoundDocumentStorageHeader();
// The first directory entry always represents the root storage entry
byte[] storageNameBytes = directoryReader.ReadBytes(64);
ushort storageNameLength = directoryReader.ReadUInt16();
byte[] storageNameValidBytes = new byte[storageNameLength];
Array.Copy(storageNameBytes, 0, storageNameValidBytes, 0, storageNameValidBytes.Length);
string storageName = System.Text.Encoding.Unicode.GetString(storageNameValidBytes);
storageName = storageName.TrimNull();
// if (storageName.Length != storageNameLength) throw new InvalidDataFormatException("Sanity check: storage name length is not actual length of storage name");
sh.Name = storageName;
sh.StorageType = (CompoundDocumentStorageType)directoryReader.ReadByte();
sh.NodeColor = (CompoundDocumentStorageColor) directoryReader.ReadByte();
sh.LeftChildNodeDirectoryID = directoryReader.ReadInt32();
sh.RightChildNodeDirectoryID = directoryReader.ReadInt32();
// directory ID of the root node entry of the red-black tree of all members of the root storage
sh.RootNodeEntryDirectoryID = directoryReader.ReadInt32();
sh.UniqueIdentifier = directoryReader.ReadGuid();
sh.Flags = (CompoundDocumentStorageFlags) directoryReader.ReadUInt32();
sh.CreationTimestamp = ReadDateTime(directoryReader);
sh.ModificationTimestamp = ReadDateTime(directoryReader);
// SecID of first sector or short-sector, if this entry refers to a stream
// SecID of first sector of the short-stream container stream, if this is the root storage entry
// 0 otherwise
sh.FirstSectorIndex = directoryReader.ReadInt32();
// Total stream size in bytes, if this entry refers to a stream,
// total size of the short-stream container stream, if this is the root storage entry
// 0 otherwise
sh.Length = directoryReader.ReadInt32();
sh.Unused3 = directoryReader.ReadInt32();
return sh;
}
private void WriteStorageHeader(Writer directoryWriter, CompoundDocumentStorageHeader sh)
{
// The first directory entry always represents the root storage entry
byte[] storageNameValidBytes = System.Text.Encoding.Unicode.GetBytes(sh.Name);
byte[] storageNameBytes = new byte[64];
Array.Copy(storageNameValidBytes, 0, storageNameBytes, 0, Math.Min(storageNameValidBytes.Length, storageNameBytes.Length));
directoryWriter.WriteBytes(storageNameBytes);
directoryWriter.WriteUInt16((ushort)sh.Name.Length);
directoryWriter.WriteByte((byte)sh.StorageType);
directoryWriter.WriteByte((byte)sh.NodeColor);
directoryWriter.WriteInt32(sh.LeftChildNodeDirectoryID);
directoryWriter.WriteInt32(sh.RightChildNodeDirectoryID);
// directory ID of the root node entry of the red-black tree of all members of the root storage
directoryWriter.WriteInt32(sh.RootNodeEntryDirectoryID);
directoryWriter.WriteGuid(sh.UniqueIdentifier);
directoryWriter.WriteUInt32((uint)sh.Flags);
WriteDateTime(directoryWriter, sh.CreationTimestamp);
WriteDateTime(directoryWriter, sh.ModificationTimestamp);
// SecID of first sector or short-sector, if this entry refers to a stream
// SecID of first sector of the short-stream container stream, if this is the root storage entry
// 0 otherwise
directoryWriter.WriteInt32(sh.FirstSectorIndex);
// Total stream size in bytes, if this entry refers to a stream,
// total size of the short-stream container stream, if this is the root storage entry
// 0 otherwise
directoryWriter.WriteInt32(sh.Length);
directoryWriter.WriteInt32(sh.Unused3);
}
private void WriteDateTime(Writer directoryWriter, DateTime creationTimestamp)
{
long value = 0;
directoryWriter.WriteInt64(value);
}
private DateTime ReadDateTime(Reader reader)
{
long value = reader.ReadInt64();
return DateTime.Now;
}
private void WriteLog(FileSystemObjectModel fsom)
{
if (LogPath == null) return;
@ -369,10 +425,10 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
}
}
private byte[] ReadDirectoryData(Reader reader)
private byte[] ReadDirectoryData(Reader reader, CompoundDocumentHeader header)
{
List<int> directorySectors = new List<int>();
int currentSector = (int)mvarDirectoryStreamFirstSectorID;
int currentSector = (int)header.DirectoryStreamFirstSectorID;
while (currentSector != -2)
{
directorySectors.Add(currentSector);
@ -386,12 +442,12 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
}
}
byte[] directoryData = new byte[mvarSectorSize * directorySectors.Count];
byte[] directoryData = new byte[SectorSize * directorySectors.Count];
for (int i = 0; i < directorySectors.Count; i++)
{
SeekToSector(directorySectors[i]);
byte[] sectorData = reader.ReadBytes(mvarSectorSize);
Array.Copy(sectorData, 0, directoryData, (i * mvarSectorSize), mvarSectorSize);
byte[] sectorData = reader.ReadBytes(SectorSize);
Array.Copy(sectorData, 0, directoryData, (i * SectorSize), SectorSize);
}
return directoryData;
}
@ -412,7 +468,7 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
List<int> sectors = new List<int>();
byte[] sectorData = null;
if (streamLength < mvarMinimumStandardStreamSize)
if (streamLength < MinimumStandardStreamSize)
{
// use the short-sector allocation table
int sector = firstSectorOfStream;
@ -434,6 +490,8 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
Array.Copy(mvarShortSectorContainerStreamData, (sectors[i] * ShortSectorSize), sectorData, i * ShortSectorSize, ShortSectorSize);
}
Array.Resize(ref sectorData, streamLength);
}
else
{
@ -528,50 +586,100 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
Writer writer = base.Accessor.Writer;
// The header is always located at the beginning of the file, and its size is
// exactly 512 bytes. This implies that the first sector (0) always starts at
// file offset 512.
writer.WriteBytes(VALID_SIGNATURE);
writer.WriteGuid(UniqueIdentifier);
writer.WriteUInt16((ushort)FormatVersion.Minor);
writer.WriteUInt16((ushort)FormatVersion.Major);
CompoundDocumentHeader header = new CompoundDocumentHeader();
header.Signature = VALID_SIGNATURE;
header.UniqueIdentifier = UniqueIdentifier;
header.MinorVersion = (ushort)FormatVersion.Minor;
header.MajorVersion = (ushort)FormatVersion.Major;
switch (Endianness)
{
case Endianness.LittleEndian:
case Endianness.LittleEndian:
{
writer.WriteBytes(new byte[] { 0xFE, 0xFF });
header.ByteOrderIdentifier = new byte[] { 0xFE, 0xFF };
break;
}
case Endianness.BigEndian:
case Endianness.BigEndian:
{
writer.WriteBytes(new byte[] { 0xFF, 0xFE });
header.ByteOrderIdentifier = new byte[] { 0xFF, 0xFE };
break;
}
}
WriteCompoundDocumentHeader(writer, header);
if (fsom.Folders.Count != 1)
{
throw new ObjectModelNotSupportedException("underlying File System must contain exactly ONE root folder, which should be named 'Root Entry'");
}
if (fsom.Folders[0].Name != "Root Entry")
{
// we should probably warn the user that this is not kosher, but we will happily write it
}
// FIXME: this is NOT correct
SeekToSector(16);
CompoundDocumentStorageHeader shRootEntry = new CompoundDocumentStorageHeader();
shRootEntry.Name = fsom.Folders[0].Name;
WriteStorageHeader(writer, shRootEntry);
foreach (File f in fsom.Folders[0].Files)
{
CompoundDocumentStorageHeader sh = new CompoundDocumentStorageHeader();
sh.Name = f.Name;
sh.StorageType = CompoundDocumentStorageType.UserStream;
sh.NodeColor = CompoundDocumentStorageColor.Red;
sh.LeftChildNodeDirectoryID = 0;
sh.RightChildNodeDirectoryID = 0;
sh.RootNodeEntryDirectoryID = 0;
sh.UniqueIdentifier = Guid.Empty;
sh.Flags = CompoundDocumentStorageFlags.None;
sh.CreationTimestamp = DateTime.Now;
sh.ModificationTimestamp = f.ModificationTimestamp;
sh.FirstSectorIndex = 0;
sh.Length = (int)f.Size;
sh.Unused3 = 0;
WriteStorageHeader(writer, sh);
}
}
private void WriteCompoundDocumentHeader(Writer writer, CompoundDocumentHeader header)
{
// The header is always located at the beginning of the file, and its size is
// exactly 512 bytes. This implies that the first sector (0) always starts at
// file offset 512.
writer.WriteBytes(header.Signature);
writer.WriteGuid(header.UniqueIdentifier);
writer.WriteUInt16(header.MinorVersion);
writer.WriteUInt16(header.MajorVersion);
writer.WriteBytes(header.ByteOrderIdentifier);
if (ShortSectorSize > SectorSize) throw new InvalidDataFormatException("Short sector size (" + ShortSectorSize.ToString() + ") exceeds sector size (" + SectorSize.ToString() + ")");
// get the 2-root of SectorSize
ushort uSectorSize = (ushort)(Math.Log10(SectorSize) / Math.Log10(2));
writer.WriteUInt16(uSectorSize);
header.SectorSize = (ushort)(Math.Log10(SectorSize) / Math.Log10(2));
writer.WriteUInt16(header.SectorSize);
// get the 2-root of SectorSize
ushort uShortSectorSize = (ushort)(Math.Log10(ShortSectorSize) / Math.Log10(2));
writer.WriteUInt16(uShortSectorSize);
header.ShortSectorSize = (ushort)(Math.Log10(ShortSectorSize) / Math.Log10(2));
writer.WriteUInt16(header.ShortSectorSize);
writer.WriteBytes(new byte[10]); // unused?
header.Unused1 = new byte[6];
writer.WriteBytes(header.Unused1); // unused?
writer.WriteUInt32(mvarSectorAllocationTableSize);
writer.WriteUInt32(mvarDirectoryStreamFirstSectorID);
writer.WriteUInt32(0);
writer.WriteUInt32(mvarMinimumStandardStreamSize);
writer.WriteUInt32(header.DirectorySectorCount);
writer.WriteUInt32(header.SectorAllocationTableSize);
writer.WriteUInt32(header.DirectoryStreamFirstSectorID);
writer.WriteUInt32(header.TransactionSignatureNumber);
writer.WriteUInt32(header.MinimumStandardStreamSize);
writer.WriteInt32(mvarShortSectorAllocationTableFirstSectorID);
writer.WriteInt32(mvarShortSectorAllocationTableSize);
writer.WriteInt32(mvarMasterSectorAllocationTableFirstSectorID);
writer.WriteInt32(mvarMasterSectorAllocationTableSize);
writer.WriteInt32(header.ShortSectorAllocationTableFirstSectorID);
writer.WriteInt32(header.ShortSectorAllocationTableSize);
writer.WriteInt32(header.MasterSectorAllocationTableFirstSectorID);
writer.WriteInt32(header.MasterSectorAllocationTableSize);
#region Read Master Sector Allocation Table
// First part of the master sector allocation table, containing 109 SecIDs
@ -579,8 +687,8 @@ namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
writer.WriteInt32Array(masterSectorAllocationTable);
// TODO: test this! when MSAT contains more than 109 SecIDs
int countForMSAT = (int)((double)mvarSectorSize / 4);
int nextSectorForMSAT = mvarMasterSectorAllocationTableFirstSectorID;
int countForMSAT = (int)((double)SectorSize / 4);
int nextSectorForMSAT = header.MasterSectorAllocationTableFirstSectorID;
int nextPositionForMSAT = masterSectorAllocationTable.Length;
#endregion

View File

@ -0,0 +1,69 @@
//
// CompoundDocumentHeader.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
public struct CompoundDocumentHeader
{
public byte[] Signature { get; set; }
public Guid UniqueIdentifier { get; set; }
public ushort MinorVersion { get; set; }
public ushort MajorVersion { get; set; }
public byte[] ByteOrderIdentifier { get; set; }
public ushort SectorSize { get; set; }
public ushort ShortSectorSize { get; set; }
public byte[] Unused1 { get; set; }
public uint DirectorySectorCount { get; set; }
/// <summary>
/// Total number of sectors used for the sector allocation table
/// </summary>
public uint SectorAllocationTableSize { get; set; }
/// <summary>
/// Sector ID of the first sector of the directory stream
/// </summary>
public uint DirectoryStreamFirstSectorID { get; set; }
public uint TransactionSignatureNumber { get; set; }
/// <summary>
/// Minimum size of a standard stream (in bytes). Minimum allowed and most-used size is
/// 4096 bytes. Streams with an actual size smaller than (and not equal to) this value
/// are stored as short-streams.
/// </summary>
public uint MinimumStandardStreamSize { get; set; } // = 4096;
/// <summary>
/// Sector ID of the first sector of the short-sector allocation table (or
/// <see cref="CompoundDocumentKnownSectorID.EndOfChain" /> if not extant).
/// </summary>
/// <value>The short sector allocation table first sector identifier.</value>
public int ShortSectorAllocationTableFirstSectorID { get; set; }
public int ShortSectorAllocationTableSize { get; set; }
/// <summary>
/// Sector ID of the first sector of the master sector allocation table (or
/// <see cref="CompoundDocumentKnownSectorID.EndOfChain" /> if no additional sectors
/// used).
/// </summary>
public int MasterSectorAllocationTableFirstSectorID { get; set; }
/// <summary>
/// Total number of sectors used for the master sector allocation table.
/// </summary>
public int MasterSectorAllocationTableSize { get; set; }
public int[] MasterSectorAllocationTable { get; set; }
}
}

View File

@ -0,0 +1,29 @@
//
// CompoundDocumentStorageColor.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
public enum CompoundDocumentStorageColor : byte
{
Red = 0x00,
Black = 0x01
}
}

View File

@ -0,0 +1,29 @@
//
// CompoundDocumentStorageFlags.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
[Flags]
public enum CompoundDocumentStorageFlags
{
None = 0
}
}

View File

@ -0,0 +1,40 @@
//
// CompoundDocumentStorageHeader.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument
{
public struct CompoundDocumentStorageHeader
{
public string Name { get; set; }
public CompoundDocumentStorageType StorageType { get; set; }
public CompoundDocumentStorageColor NodeColor { get; set; }
public int LeftChildNodeDirectoryID { get; set; }
public int RightChildNodeDirectoryID { get; set; }
public int RootNodeEntryDirectoryID { get; set; }
public Guid UniqueIdentifier { get; set; }
public CompoundDocumentStorageFlags Flags { get; set; }
public DateTime CreationTimestamp { get; set; }
public DateTime ModificationTimestamp { get; set; }
public int FirstSectorIndex { get; set; }
public int Length { get; set; }
public int Unused3 { get; set; }
}
}

View File

@ -0,0 +1,128 @@
//
// MicrosoftOfficeDocumentDataFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using UniversalEditor.Accessors;
using UniversalEditor.DataFormats.CompoundDocument;
using UniversalEditor.ObjectModels.CompoundDocument;
using UniversalEditor.ObjectModels.FileSystem;
namespace UniversalEditor.DataFormats.Office
{
public abstract class MicrosoftOfficeDocumentDataFormat : CompoundDocumentDataFormat
{
protected abstract string MainDocumentStreamName { get; }
protected abstract ushort MainDocumentIdentifier { get; }
protected override void BeforeSaveInternal(Stack<ObjectModel> objectModels)
{
CompoundDocumentObjectModel fsom = new CompoundDocumentObjectModel();
Folder rootEntry = fsom.Folders.Add("Root Entry");
MemoryAccessor ma = new MemoryAccessor();
ma.Writer.WriteUInt16(MainDocumentIdentifier);
ushort fib_nFib = 0;
ma.Writer.WriteUInt16(fib_nFib);
ushort fib_unused1 = 0;
ma.Writer.WriteUInt16(fib_unused1);
ushort fib_lid = 0;
ma.Writer.WriteUInt16(fib_lid);
ushort fib_pnNext = 0;
ma.Writer.WriteUInt16(fib_pnNext);
ushort fib_flags = 0;
ma.Writer.WriteUInt16(fib_flags);
ushort fib_nFibBack = 0;
ma.Writer.WriteUInt16(fib_nFibBack);
uint fib_lKey = 0;
ma.Writer.WriteUInt32(fib_lKey);
byte fib_envr = 0;
ma.Writer.WriteByte(fib_envr);
byte fib_flags2 = 0;
ma.Writer.WriteByte(fib_flags2);
ushort fib_reserved3 = 0;
ma.Writer.WriteUInt16(fib_reserved3);
ushort fib_reserved4 = 0;
ma.Writer.WriteUInt16(fib_reserved4);
uint fib_reserved5 = 0;
ma.Writer.WriteUInt32(fib_reserved5);
uint fib_reserved6 = 0;
ma.Writer.WriteUInt32(fib_reserved6);
ma.Close();
File mainDocument = rootEntry.Files.Add(MainDocumentStreamName, ma.ToArray());
objectModels.Push(fsom);
base.BeforeSaveInternal(objectModels);
}
protected override void AfterLoadInternal(Stack<ObjectModel> objectModels)
{
base.AfterLoadInternal(objectModels);
CompoundDocumentObjectModel fsom = objectModels.Pop() as CompoundDocumentObjectModel;
File mainDocument = fsom.Folders["Root Entry"].Files[MainDocumentStreamName];
if (mainDocument == null)
{
throw new InvalidDataFormatException(String.Format("compound document file does not contain a '{0}' stream", MainDocumentStreamName));
}
MemoryAccessor ma = new MemoryAccessor(mainDocument.GetData());
ushort fib_wIdent = ma.Reader.ReadUInt16();
if (fib_wIdent != MainDocumentIdentifier)
{
throw new InvalidDataFormatException(String.Format("file information block does not contain document signature 0x{0}", MainDocumentIdentifier.ToString("x")));
}
ushort fib_nFib = ma.Reader.ReadUInt16();
ushort fib_unused1 = ma.Reader.ReadUInt16();
ushort fib_lid = ma.Reader.ReadUInt16();
ushort fib_pnNext = ma.Reader.ReadUInt16();
ushort fib_flags = ma.Reader.ReadUInt16();
ushort fib_nFibBack = ma.Reader.ReadUInt16();
uint fib_lKey = ma.Reader.ReadUInt32();
byte fib_envr = ma.Reader.ReadByte();
byte fib_flags2 = ma.Reader.ReadByte();
ushort fib_reserved3 = ma.Reader.ReadUInt16();
ushort fib_reserved4 = ma.Reader.ReadUInt16();
uint fib_reserved5 = ma.Reader.ReadUInt32();
uint fib_reserved6 = ma.Reader.ReadUInt32();
objectModels.Push(fsom);
}
}
}

View File

@ -19,11 +19,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using UniversalEditor.Accessors;
using UniversalEditor.DataFormats.CompoundDocument;
using UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument;
using UniversalEditor.DataFormats.Office;
using UniversalEditor.ObjectModels.CompoundDocument;
using UniversalEditor.ObjectModels.FileSystem;
using UniversalEditor.ObjectModels.Office;
using UniversalEditor.ObjectModels.Text.Formatted;
namespace UniversalEditor.DataFormats.Text.Formatted.DOC
@ -31,7 +35,7 @@ namespace UniversalEditor.DataFormats.Text.Formatted.DOC
/// <summary>
/// Provides a <see cref="DataFormat" /> to manipulate Microsoft Office Word DOC files.
/// </summary>
public class DOCDataFormat : CompoundDocumentDataFormat
public class DOCDataFormat : MicrosoftOfficeDocumentDataFormat
{
private static DataFormatReference _dfr;
protected override DataFormatReference MakeReferenceInternal()
@ -44,18 +48,26 @@ namespace UniversalEditor.DataFormats.Text.Formatted.DOC
return _dfr;
}
protected override string MainDocumentStreamName => "WordDocument";
protected override ushort MainDocumentIdentifier => 0xA5EC;
protected override void BeforeLoadInternal(Stack<ObjectModel> objectModels)
{
objectModels.Push(new MicrosoftOfficeDocumentObjectModel());
base.BeforeLoadInternal(objectModels);
objectModels.Push(new CompoundDocumentObjectModel());
}
protected override void AfterLoadInternal(Stack<ObjectModel> objectModels)
{
base.AfterLoadInternal(objectModels);
CompoundDocumentObjectModel fsom = (objectModels.Pop() as CompoundDocumentObjectModel);
MicrosoftOfficeDocumentObjectModel fsom = (objectModels.Pop() as MicrosoftOfficeDocumentObjectModel);
FormattedTextObjectModel ftom = (objectModels.Pop() as FormattedTextObjectModel);
if (fsom.AssociationTypeId != "Word.Document.8")
{
throw new InvalidDataFormatException(String.Format("association type '{0}' invalid for Microsoft Word 97-2003 data format", fsom.AssociationTypeId));
}
}
protected override void BeforeSaveInternal(Stack<ObjectModel> objectModels)
{
@ -63,7 +75,6 @@ namespace UniversalEditor.DataFormats.Text.Formatted.DOC
CompoundDocumentObjectModel fsom = new CompoundDocumentObjectModel();
objectModels.Push(fsom);
base.BeforeSaveInternal(objectModels);
}

View File

@ -0,0 +1,63 @@
//
// CompoundDocumentClipboardFormat.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
namespace UniversalEditor.ObjectModels.CompoundDocument
{
public struct CompoundDocumentClipboardFormat
{
private string _Str;
private uint _Int;
public static readonly CompoundDocumentClipboardFormat Empty;
private CompoundDocumentClipboardFormat(uint _int)
{
_Int = _int;
_Str = null;
}
private CompoundDocumentClipboardFormat(string _str)
{
_Str = _str;
_Int = 0;
}
public bool IsStandard { get { return _Str == null && _Int != 0; } }
public bool IsEmpty { get { return _Str == null && _Int == 0; } }
public override string ToString()
{
if (_Str != null)
{
return _Str;
}
return _Int.ToString();
}
public static CompoundDocumentClipboardFormat FromStandard(uint index)
{
return new CompoundDocumentClipboardFormat(index);
}
public static CompoundDocumentClipboardFormat FromString(string format)
{
return new CompoundDocumentClipboardFormat(format);
}
}
}

View File

@ -25,8 +25,20 @@ namespace UniversalEditor.ObjectModels.CompoundDocument
{
public class CompoundDocumentObjectModel : FileSystemObjectModel
{
public CompoundDocumentObjectModel()
private static ObjectModelReference _omr = null;
public string UserType { get; set; }
public CompoundDocumentClipboardFormat ClipboardFormat { get; set; }
public string AssociationTypeId { get; set; }
protected override ObjectModelReference MakeReferenceInternal()
{
if (_omr == null)
{
_omr = new ObjectModelReference(GetType());
_omr.Path = new string[] { "Microsoft", "OLE", "OLE2 Compound Document" };
}
return _omr;
}
}
}

View File

@ -0,0 +1,32 @@
//
// MicrosoftOfficeDocumentObjectModel.cs
//
// Author:
// Michael Becker <alcexhim@gmail.com>
//
// Copyright (c) 2022 Mike Becker's Software
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using UniversalEditor.ObjectModels.CompoundDocument;
namespace UniversalEditor.ObjectModels.Office
{
public class MicrosoftOfficeDocumentObjectModel : CompoundDocumentObjectModel
{
public MicrosoftOfficeDocumentObjectModel()
{
}
}
}

View File

@ -149,6 +149,17 @@
<Compile Include="DataFormats\FileSystem\Microsoft\MSCompressed\Internal\SZDDComp.cs" />
<Compile Include="DataFormats\CompoundDocument\CompoundDocumentDataFormat.cs" />
<Compile Include="ObjectModels\CompoundDocument\CompoundDocumentObjectModel.cs" />
<Compile Include="ObjectModels\CompoundDocument\CompoundDocumentClipboardFormat.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\CompoundDocument\CompoundDocumentStorageHeader.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\CompoundDocument\CompoundDocumentStorageFlags.cs" />
<Compile Include="ObjectModels\Office\MicrosoftOfficeDocumentObjectModel.cs" />
<Compile Include="DataFormats\Office\MicrosoftOfficeDocumentDataFormat.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\CompoundDocument\CompoundDocumentStorageColor.cs" />
<Compile Include="DataFormats\FileSystem\Microsoft\CompoundDocument\CompoundDocumentHeader.cs" />
<Compile Include="DataFormats\CompoundDocument\SummaryInformation\SummaryInformationDataFormat.cs" />
<Compile Include="DataFormats\CompoundDocument\SummaryInformation\PropertySetInfo.cs" />
<Compile Include="DataFormats\CompoundDocument\SummaryInformation\PropertyInfo.cs" />
<Compile Include="DataFormats\CompoundDocument\SummaryInformation\PropertySetPropertyType.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Libraries\UniversalEditor.Compression\UniversalEditor.Compression.csproj">
@ -207,6 +218,9 @@
<Folder Include="Associations\Text\Formatted\" />
<Folder Include="DataFormats\CompoundDocument\" />
<Folder Include="ObjectModels\CompoundDocument\" />
<Folder Include="ObjectModels\Office\" />
<Folder Include="DataFormats\Office\" />
<Folder Include="DataFormats\CompoundDocument\SummaryInformation\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Associations\Help\WinHelp.uexml" />