From 04a46da1773dd2114f09435ecd45d1b310c6d666 Mon Sep 17 00:00:00 2001 From: Michael Becker Date: Sat, 6 Aug 2022 19:13:23 -0400 Subject: [PATCH] improve support for OLE2 compound document base format and some subformats --- .../DataFormats/Markup/XML/.#XMLDataFormat.cs | 0 .../FamilyTreeMaker/Windows/FTWDataFormat.cs | 4 +- .../Microsoft/VisualStudio/SUODataFormat.cs | 7 +- .../Associations/CompoundDocument.uexml | 28 +- .../Associations/Text/Formatted/DOC.uexml | 3 + .../CompoundDocumentDataFormat.cs | 106 ++++ .../SummaryInformation/PropertyInfo.cs | 29 + .../SummaryInformation/PropertySetInfo.cs | 29 + .../PropertySetPropertyType.cs | 169 ++++++ .../SummaryInformationDataFormat.cs | 560 ++++++++++++++++++ .../CompoundDocumentBaseDataFormat.cs | 480 +++++++++------ .../CompoundDocumentHeader.cs | 69 +++ .../CompoundDocumentStorageColor.cs | 29 + .../CompoundDocumentStorageFlags.cs | 29 + .../CompoundDocumentStorageHeader.cs | 40 ++ .../MicrosoftOfficeDocumentDataFormat.cs | 128 ++++ .../Text/Formatted/DOC/DOCDataFormat.cs | 19 +- .../CompoundDocumentClipboardFormat.cs | 63 ++ .../CompoundDocumentObjectModel.cs | 14 +- .../MicrosoftOfficeDocumentObjectModel.cs | 32 + .../UniversalEditor.Plugins.Microsoft.csproj | 14 + 21 files changed, 1654 insertions(+), 198 deletions(-) delete mode 100644 Libraries/UniversalEditor.Essential/DataFormats/Markup/XML/.#XMLDataFormat.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertyInfo.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetInfo.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetPropertyType.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/SummaryInformationDataFormat.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentHeader.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageColor.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageFlags.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageHeader.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Office/MicrosoftOfficeDocumentDataFormat.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentClipboardFormat.cs create mode 100644 Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/Office/MicrosoftOfficeDocumentObjectModel.cs diff --git a/Libraries/UniversalEditor.Essential/DataFormats/Markup/XML/.#XMLDataFormat.cs b/Libraries/UniversalEditor.Essential/DataFormats/Markup/XML/.#XMLDataFormat.cs deleted file mode 100644 index e69de29b..00000000 diff --git a/Plugins/UniversalEditor.Plugins.Genealogy/DataFormats/FamilyTreeMaker/Windows/FTWDataFormat.cs b/Plugins/UniversalEditor.Plugins.Genealogy/DataFormats/FamilyTreeMaker/Windows/FTWDataFormat.cs index fd05cd8e..4ee62a18 100644 --- a/Plugins/UniversalEditor.Plugins.Genealogy/DataFormats/FamilyTreeMaker/Windows/FTWDataFormat.cs +++ b/Plugins/UniversalEditor.Plugins.Genealogy/DataFormats/FamilyTreeMaker/Windows/FTWDataFormat.cs @@ -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"); diff --git a/Plugins/UniversalEditor.Plugins.Microsoft.VisualStudio/DataFormats/PropertyList/Microsoft/VisualStudio/SUODataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft.VisualStudio/DataFormats/PropertyList/Microsoft/VisualStudio/SUODataFormat.cs index 34c6302d..d4e8adc0 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft.VisualStudio/DataFormats/PropertyList/Microsoft/VisualStudio/SUODataFormat.cs +++ b/Plugins/UniversalEditor.Plugins.Microsoft.VisualStudio/DataFormats/PropertyList/Microsoft/VisualStudio/SUODataFormat.cs @@ -20,6 +20,8 @@ // along with this program. If not, see . 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 objectModels) { diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/Associations/CompoundDocument.uexml b/Plugins/UniversalEditor.Plugins.Microsoft/Associations/CompoundDocument.uexml index 7e66e1fd..f8ab10fa 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/Associations/CompoundDocument.uexml +++ b/Plugins/UniversalEditor.Plugins.Microsoft/Associations/CompoundDocument.uexml @@ -1,6 +1,19 @@ + + + + + + + + + + + + + @@ -8,11 +21,22 @@ - + - + + + + + + + + + + + + diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/Associations/Text/Formatted/DOC.uexml b/Plugins/UniversalEditor.Plugins.Microsoft/Associations/Text/Formatted/DOC.uexml index ec7636bc..a71c003f 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/Associations/Text/Formatted/DOC.uexml +++ b/Plugins/UniversalEditor.Plugins.Microsoft/Associations/Text/Formatted/DOC.uexml @@ -5,6 +5,9 @@ + + *.doc + diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/CompoundDocumentDataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/CompoundDocumentDataFormat.cs index 7a398111..2dadb2cb 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/CompoundDocumentDataFormat.cs +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/CompoundDocumentDataFormat.cs @@ -19,12 +19,118 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . 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 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(); + } } } diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertyInfo.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertyInfo.cs new file mode 100644 index 00000000..f3c119a4 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertyInfo.cs @@ -0,0 +1,29 @@ +// +// PropertyInfo.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation +{ + public struct PropertyInfo + { + public uint Identifier { get; set; } + public uint Offset { get; set; } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetInfo.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetInfo.cs new file mode 100644 index 00000000..cd349fd5 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetInfo.cs @@ -0,0 +1,29 @@ +// +// CompoundDocumentPropertySetInfo.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation +{ + public struct PropertySetInfo + { + public Guid Guid { get; set; } + public uint Offset { get; set; } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetPropertyType.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetPropertyType.cs new file mode 100644 index 00000000..78c25019 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/PropertySetPropertyType.cs @@ -0,0 +1,169 @@ +// +// PropertySetPropertyType.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.CompoundDocument.SummaryInformation +{ + public enum PropertySetPropertyType : ushort + { + /// + /// Type is undefined, and the minimum property set version is 0. + /// + Empty = 0x0000, + /// + /// Type is null, and the minimum property set version is 0. + /// + Null = 0x0001, + /// + /// Type is 16-bit signed integer, and the minimum property set version is 0. + /// + I2 = 0x0002, + /// + /// Type is 32-bit signed integer, and the minimum property set version is 0. + /// + I4 = 0x0003, + /// + /// Type is 4-byte (single-precision) IEEE floating-point number, and the + /// minimum property set version is 0. + /// + R4 = 0x0004, + /// + /// Type is 8-byte (double-precision) IEEE floating-point number, and the + /// minimum property set version is 0. + /// + R8 = 0x0005, + /// + /// Type is CURRENCY, and the minimum property set version is 0. + /// + Currency = 0x0006, + /// + /// Type is DATE (OLE Automation), and the minimum property set version is 0. + /// + Date = 0x0007, + /// + /// Type is CodePageString, and the minimum property set version is 0. + /// + BStr = 0x0008, + /// + /// Type is HRESULT, and the minimum property set version is 0. + /// + Error = 0x000A, + /// + /// Type is VARIANT_BOOL, and the minimum property set version is 0. + /// + Bool = 0x000B, + /// + /// Type is DECIMAL, and the minimum property set version is 0. + /// + Decimal = 0x000E, + /// + /// Type is 1-byte signed integer, and the minimum property set version is 1. + /// + I1 = 0x0010, + /// + /// Type is 1-byte unsigned integer, and the minimum property set version is 0. + /// + UI1 = 0x0011, + /// + /// Type is 2-byte unsigned integer, and the minimum property set version is 0. + /// + UI2 = 0x0012, + /// + /// Type is 4-byte unsigned integer, and the minimum property set version is 0. + /// + UI4 = 0x0013, + /// + /// Type is 8-byte signed integer, and the minimum property set version is 0. + /// + I8 = 0x0014, + /// + /// Type is 8-byte unsigned integer, and the minimum property set version is 0. + /// + UI8 = 0x0015, + /// + /// Type is 4-byte signed integer, and the minimum property set version is 1. + /// + Int = 0x0016, + /// + /// Type is 4-byte unsigned integer, and the minimum property set version is 1. + /// + UInt = 0x0017, + /// + /// Type is CodePageString, and the minimum property set version is 0. + /// + LPStr = 0x001E, + /// + /// Type is UnicodeString, and the minimum property set version is 0. + /// + LPWStr = 0x001F, + /// + /// Type is FILETIME, and the minimum property set version is 0. + /// + FileTime = 0x0040, + /// + /// Type is binary large object (BLOB), and the minimum property set version is 0. + /// + Blob = 0x0041, + /// + /// Type is Stream, and the minimum property set version is 0. VT_STREAM is not + /// allowed in a simple property set. + /// + Stream = 0x0042, + /// + /// Type is Storage, and the minimum property set version is 0. VT_STORAGE is not + /// allowed in a simple property set. + /// + Storage = 0x0043, + /// + /// 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. + /// + StreamedObject = 0x0044, + /// + /// 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. + /// + StoredObject = 0x0045, + /// + /// Type is BLOB representing an object in an application-specific manner. The minimum + /// property set version is 0. + /// + BlobObject = 0x0046, + /// + /// Type is PropertyIdentifier, and the minimum property set version is 0. + /// + PropertyIdentifier = 0x0047, + /// + /// Type is CLSID, and the minimum property set version is 0. + /// + CLSID = 0x0048, + /// + /// 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. + /// + VersionedStream = 0x0049, + + Vector = 0x1000, + Array = 0x2000 + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/SummaryInformationDataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/SummaryInformationDataFormat.cs new file mode 100644 index 00000000..353f3120 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/CompoundDocument/SummaryInformation/SummaryInformationDataFormat.cs @@ -0,0 +1,560 @@ +// +// SummaryInformationDataFormat.cs +// +// Author: +// Michael Becker +// +// 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 . +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", 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("info", pi); + propertySetGroups[i].Items.Add(property); + } + } + + for (int i = 0; i < propertySetInfos.Length; i++) + { + IEnumerable properties = propertySetGroups[i].Items.OfType(); + foreach (Property property in properties) + { + PropertyInfo pi = property.GetExtraData("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 groups = plom.Items.OfType(); + Accessor.Writer.WriteUInt32((uint)groups.Count()); + uint offset = (uint)(28 + (20 * groups.Count())); + + foreach (Group group in groups) + { + Accessor.Writer.WriteGuid(group.GetExtraData("guid")); + Accessor.Writer.WriteUInt32(offset); + + IEnumerable properties = group.Items.OfType(); + offset += (uint)(8 + (8 * properties.Count())); + } + + offset -= (uint) Accessor.Position; + + foreach (Group group in groups) + { + IEnumerable properties = group.Items.OfType(); + + 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 properties = group.Items.OfType(); + + 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; + } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentBaseDataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentBaseDataFormat.cs index a5b30759..2f498ee6 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentBaseDataFormat.cs +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentBaseDataFormat.cs @@ -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; } } - - /// - /// Total number of sectors used for the sector allocation table - /// - private uint mvarSectorAllocationTableSize = 0; - /// - /// Sector ID of the first sector of the directory stream - /// - private uint mvarDirectoryStreamFirstSectorID = 0; - - private uint mvarMinimumStandardStreamSize = 4096; - /// - /// 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. - /// - public uint MinimumStandardStreamSize { get { return mvarMinimumStandardStreamSize; } set { mvarMinimumStandardStreamSize = value; } } - - /// - /// Sector ID of the first sector of the short-sector allocation table (or - /// if not extant). - /// - private int mvarShortSectorAllocationTableFirstSectorID = 0; - /// - /// Total number of sectors used for the short-sector allocation table. - /// - private int mvarShortSectorAllocationTableSize = 0; - /// - /// Sector ID of the first sector of the master sector allocation table (or - /// if no additional sectors - /// used). - /// - private int mvarMasterSectorAllocationTableFirstSectorID = 0; - /// - /// Total number of sectors used for the master sector allocation table. - /// - 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 shortSectorAllocationTableSectors = new List(); 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(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(); - 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 listHeaders = new List(); 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 shortStreamContainerStreamSectors = new List(); @@ -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 directorySectors = new List(); - 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 sectors = new List(); 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 diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentHeader.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentHeader.cs new file mode 100644 index 00000000..a3f6f13a --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentHeader.cs @@ -0,0 +1,69 @@ +// +// CompoundDocumentHeader.cs +// +// Author: +// Michael Becker +// +// 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 . +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; } + /// + /// Total number of sectors used for the sector allocation table + /// + public uint SectorAllocationTableSize { get; set; } + /// + /// Sector ID of the first sector of the directory stream + /// + public uint DirectoryStreamFirstSectorID { get; set; } + public uint TransactionSignatureNumber { get; set; } + /// + /// 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. + /// + public uint MinimumStandardStreamSize { get; set; } // = 4096; + /// + /// Sector ID of the first sector of the short-sector allocation table (or + /// if not extant). + /// + /// The short sector allocation table first sector identifier. + public int ShortSectorAllocationTableFirstSectorID { get; set; } + public int ShortSectorAllocationTableSize { get; set; } + /// + /// Sector ID of the first sector of the master sector allocation table (or + /// if no additional sectors + /// used). + /// + public int MasterSectorAllocationTableFirstSectorID { get; set; } + /// + /// Total number of sectors used for the master sector allocation table. + /// + public int MasterSectorAllocationTableSize { get; set; } + public int[] MasterSectorAllocationTable { get; set; } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageColor.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageColor.cs new file mode 100644 index 00000000..b7edcb06 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageColor.cs @@ -0,0 +1,29 @@ +// +// CompoundDocumentStorageColor.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument +{ + public enum CompoundDocumentStorageColor : byte + { + Red = 0x00, + Black = 0x01 + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageFlags.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageFlags.cs new file mode 100644 index 00000000..809f1fcd --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageFlags.cs @@ -0,0 +1,29 @@ +// +// CompoundDocumentStorageFlags.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +namespace UniversalEditor.DataFormats.FileSystem.Microsoft.CompoundDocument +{ + [Flags] + public enum CompoundDocumentStorageFlags + { + None = 0 + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageHeader.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageHeader.cs new file mode 100644 index 00000000..613d4b97 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/FileSystem/Microsoft/CompoundDocument/CompoundDocumentStorageHeader.cs @@ -0,0 +1,40 @@ +// +// CompoundDocumentStorageHeader.cs +// +// Author: +// Michael Becker +// +// 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 . +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; } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Office/MicrosoftOfficeDocumentDataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Office/MicrosoftOfficeDocumentDataFormat.cs new file mode 100644 index 00000000..7d6aee1a --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Office/MicrosoftOfficeDocumentDataFormat.cs @@ -0,0 +1,128 @@ +// +// MicrosoftOfficeDocumentDataFormat.cs +// +// Author: +// Michael Becker +// +// 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 . +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 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 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); + } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Text/Formatted/DOC/DOCDataFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Text/Formatted/DOC/DOCDataFormat.cs index 2892d8c8..9d67c93a 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Text/Formatted/DOC/DOCDataFormat.cs +++ b/Plugins/UniversalEditor.Plugins.Microsoft/DataFormats/Text/Formatted/DOC/DOCDataFormat.cs @@ -19,11 +19,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +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 /// /// Provides a to manipulate Microsoft Office Word DOC files. /// - 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 objectModels) { + objectModels.Push(new MicrosoftOfficeDocumentObjectModel()); base.BeforeLoadInternal(objectModels); - objectModels.Push(new CompoundDocumentObjectModel()); } protected override void AfterLoadInternal(Stack 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 objectModels) { @@ -63,7 +75,6 @@ namespace UniversalEditor.DataFormats.Text.Formatted.DOC CompoundDocumentObjectModel fsom = new CompoundDocumentObjectModel(); - objectModels.Push(fsom); base.BeforeSaveInternal(objectModels); } diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentClipboardFormat.cs b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentClipboardFormat.cs new file mode 100644 index 00000000..d4b5c871 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentClipboardFormat.cs @@ -0,0 +1,63 @@ +// +// CompoundDocumentClipboardFormat.cs +// +// Author: +// Michael Becker +// +// 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 . +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); + } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentObjectModel.cs b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentObjectModel.cs index 249076c3..ed56e283 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentObjectModel.cs +++ b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/CompoundDocument/CompoundDocumentObjectModel.cs @@ -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; } } } diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/Office/MicrosoftOfficeDocumentObjectModel.cs b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/Office/MicrosoftOfficeDocumentObjectModel.cs new file mode 100644 index 00000000..06d13164 --- /dev/null +++ b/Plugins/UniversalEditor.Plugins.Microsoft/ObjectModels/Office/MicrosoftOfficeDocumentObjectModel.cs @@ -0,0 +1,32 @@ +// +// MicrosoftOfficeDocumentObjectModel.cs +// +// Author: +// Michael Becker +// +// 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 . +using System; +using UniversalEditor.ObjectModels.CompoundDocument; + +namespace UniversalEditor.ObjectModels.Office +{ + public class MicrosoftOfficeDocumentObjectModel : CompoundDocumentObjectModel + { + public MicrosoftOfficeDocumentObjectModel() + { + } + } +} diff --git a/Plugins/UniversalEditor.Plugins.Microsoft/UniversalEditor.Plugins.Microsoft.csproj b/Plugins/UniversalEditor.Plugins.Microsoft/UniversalEditor.Plugins.Microsoft.csproj index 9c976a90..cabd06b2 100644 --- a/Plugins/UniversalEditor.Plugins.Microsoft/UniversalEditor.Plugins.Microsoft.csproj +++ b/Plugins/UniversalEditor.Plugins.Microsoft/UniversalEditor.Plugins.Microsoft.csproj @@ -149,6 +149,17 @@ + + + + + + + + + + + @@ -207,6 +218,9 @@ + + +