diff --git a/MatFileHandler.Tests/MatFileReaderTests.cs b/MatFileHandler.Tests/MatFileReaderTests.cs index 0757839..bdb91e8 100755 --- a/MatFileHandler.Tests/MatFileReaderTests.cs +++ b/MatFileHandler.Tests/MatFileReaderTests.cs @@ -418,6 +418,22 @@ namespace MatFileHandler.Tests Assert.That(diff - datetime[0] < TimeSpan.FromMilliseconds(1)); } + /// + /// Test string objects. + /// + [Test] + public void TestString() + { + var matFile = GetTests("good")["string"]; + var s = matFile["s"].Value as IMatObject; + var str = new StringAdapter(s); + Assert.That(str.Dimensions, Is.EquivalentTo(new[] { 4, 1 })); + Assert.That(str[0], Is.EqualTo("abc")); + Assert.That(str[1], Is.EqualTo("defgh")); + Assert.That(str[2], Is.EqualTo("абвгд")); + Assert.That(str[3], Is.EqualTo("æøå")); + } + private static AbstractTestDataFactory GetTests(string factoryName) => new MatTestDataFactory(Path.Combine(TestDirectory, factoryName)); diff --git a/MatFileHandler.Tests/test-data/good/string.mat b/MatFileHandler.Tests/test-data/good/string.mat new file mode 100644 index 0000000..b86a9c9 Binary files /dev/null and b/MatFileHandler.Tests/test-data/good/string.mat differ diff --git a/MatFileHandler/StringAdapter.cs b/MatFileHandler/StringAdapter.cs new file mode 100644 index 0000000..13cfa1d --- /dev/null +++ b/MatFileHandler/StringAdapter.cs @@ -0,0 +1,80 @@ +// Copyright 2017-2018 Alexander Luzgarev + +using System; +using System.Linq; +using System.Numerics; +using System.Text; + +namespace MatFileHandler +{ + /// + /// A better interface for using datetime objects. + /// + public class StringAdapter + { + private readonly int[] dimensions; + private readonly string[] strings; + + /// + /// Initializes a new instance of the class. + /// + /// Source datetime object. + public StringAdapter(IArray array) + { + var matObject = array as IMatObject; + if (matObject?.ClassName != "string") + { + throw new ArgumentException("The object provided is not a string."); + } + + var binaryData = matObject["any", 0] as IArrayOf; + + (dimensions, strings) = ParseBinaryData(binaryData.Data); + } + + /// + /// Gets datetime array dimensions. + /// + public int[] Dimensions => dimensions; + + /// + /// Gets string object at given position. + /// + /// Indices. + /// Value. + public string this[params int[] list] => strings[Dimensions.DimFlatten(list)]; + + private static (int[] dimensions, string[] strings) ParseBinaryData(ulong[] binaryData) + { + var numberOfDimensions = (int)binaryData[1]; + var d = new int[numberOfDimensions]; + for (var i = 0; i < numberOfDimensions; i++) + { + d[i] = (int)binaryData[i + 2]; + } + + var numberOfElements = d.NumberOfElements(); + var start = numberOfDimensions + 2; + var lengths = new int[numberOfElements]; + for (var i = 0; i < numberOfElements; i++) + { + lengths[i] = (int)binaryData[start + i]; + } + + var strings = new string[numberOfElements]; + + start += numberOfElements; + var numberOfUlongsLeft = binaryData.Length - start; + var bytes = new byte[numberOfUlongsLeft * sizeof(ulong)]; + Buffer.BlockCopy(binaryData, start * sizeof(ulong), bytes, 0, bytes.Length); + var counter = 0; + for (var i = 0; i < numberOfElements; i++) + { + strings[i] = Encoding.Unicode.GetString(bytes, counter * 2, lengths[i] * 2); + counter += lengths[i]; + } + + return (d, strings); + } + } +} \ No newline at end of file diff --git a/MatFileHandler/SubsystemData.cs b/MatFileHandler/SubsystemData.cs index ab65292..fd987b8 100644 --- a/MatFileHandler/SubsystemData.cs +++ b/MatFileHandler/SubsystemData.cs @@ -125,11 +125,9 @@ namespace MatFileHandler /// /// Initializes a new instance of the class. /// - /// Position of object in the object information table. /// A dictionary mapping the field indices to "field values" indices. - public ObjectInfo(int position, Dictionary fieldLinks) + public ObjectInfo(Dictionary fieldLinks) { - Position = position; this.fieldLinks = fieldLinks ?? throw new ArgumentNullException(nameof(fieldLinks)); } @@ -137,11 +135,6 @@ namespace MatFileHandler /// Gets mapping between the field indices and "field values" indices. /// public IReadOnlyDictionary FieldLinks => fieldLinks; - - /// - /// Gets position of object in the object information table. - /// - public int Position { get; } } } } \ No newline at end of file diff --git a/MatFileHandler/SubsystemDataReader.cs b/MatFileHandler/SubsystemDataReader.cs index 593cf6e..affe427 100644 --- a/MatFileHandler/SubsystemDataReader.cs +++ b/MatFileHandler/SubsystemDataReader.cs @@ -50,8 +50,19 @@ namespace MatFileHandler } } + var numberOfEmbeddedObjects = (offsets[4] - offsets[3] - 8) / 16; + Dictionary embeddedObjectPositionsToValues = null; + using (var stream = new MemoryStream(info, offsets[3], offsets[4] - offsets[3])) + { + using (var reader = new BinaryReader(stream)) + { + embeddedObjectPositionsToValues = + ReadEmbeddedObjectPositionsToValuesMapping(reader, numberOfEmbeddedObjects); + } + } + var numberOfObjects = ((offsets[5] - offsets[4]) / 24) - 1; - Dictionary objectClasses = null; + Dictionary objectClasses = null; using (var stream = new MemoryStream(info, offsets[4], offsets[5] - offsets[4])) { using (var reader = new BinaryReader(stream)) @@ -60,7 +71,7 @@ namespace MatFileHandler } } - var numberOfObjectPositions = objectClasses.Values.Count(x => x.objectPosition != 0); + var numberOfObjectPositions = objectClasses.Values.Count(x => x.ObjectPosition != 0); Dictionary> objectPositionsToValues = null; using (var stream = new MemoryStream(info, offsets[5], offsets[6] - offsets[5])) @@ -76,7 +87,8 @@ namespace MatFileHandler classIdToName, fieldNames, objectClasses, - objectPositionsToValues); + objectPositionsToValues, + embeddedObjectPositionsToValues); var allFields = objectInformation.Values.SelectMany(obj => obj.FieldLinks.Values); var data = new Dictionary(); @@ -84,62 +96,28 @@ namespace MatFileHandler { data[i] = TransformOpaqueData(opaqueData[i + 2], subsystemData); } + return new SubsystemData(classInformation, objectInformation, data); } - private static IArray TransformOpaqueData(IArray array, SubsystemData subsystemData) - { - if (array is MatNumericalArrayOf uintArray) - { - if (uintArray.Data[0] == 3707764736u) - { - var (dimensions, indexToObjectId, classIndex) = DataElementReader.ParseOpaqueData(uintArray.Data); - return new OpaqueLink( - uintArray.Name, - string.Empty, - string.Empty, - dimensions, - array as DataElement, - indexToObjectId, - classIndex, - subsystemData); - } - } - - return array; - } - - private static Dictionary ReadObjectClasses(BinaryReader reader, int numberOfObjects) - { - var result = new Dictionary(); - reader.ReadBytes(24); - for (var i = 0; i < numberOfObjects; i++) - { - var classId = reader.ReadInt32(); - reader.ReadBytes(12); - var objectPosition = reader.ReadInt32(); - var loadingOrder = reader.ReadInt32(); - result[i + 1] = (objectPosition, loadingOrder, classId); - } - - return result; - } - - private static (Dictionary, Dictionary) GatherClassAndObjectInformation( - Dictionary classIdToName, - string[] fieldNames, - Dictionary objectClasses, - Dictionary> objectPositionsToValues) + private static (Dictionary, Dictionary) + GatherClassAndObjectInformation( + Dictionary classIdToName, + string[] fieldNames, + Dictionary objectClasses, + Dictionary> objectPositionsToValues, + Dictionary embeddedObjectPositionsToValues) { var classInfos = new Dictionary(); + var newEmbeddedObjectPositionsToValues = new Dictionary>(); foreach (var classId in classIdToName.Keys) { var className = classIdToName[classId]; var fieldIds = new SortedSet(); foreach (var objectPosition in objectPositionsToValues.Keys) { - var keyValuePair = objectClasses.First(pair => pair.Value.objectPosition == objectPosition); - if (keyValuePair.Value.classId != classId) + var keyValuePair = objectClasses.First(pair => pair.Value.ObjectPosition == objectPosition); + if (keyValuePair.Value.ClassId != classId) { continue; } @@ -149,55 +127,48 @@ namespace MatFileHandler fieldIds.Add(fieldId); } } + + foreach (var objectPosition in embeddedObjectPositionsToValues.Keys) + { + var keyValuePair = objectClasses.First(pair => pair.Value.EmbeddedObjectPosition == objectPosition); + if (keyValuePair.Value.ClassId != classId) + { + continue; + } + + fieldIds.Add(embeddedObjectPositionsToValues[objectPosition].FieldIndex); + var d = new Dictionary(); + var embeddedInfo = embeddedObjectPositionsToValues[objectPosition]; + d[embeddedInfo.FieldIndex] = embeddedInfo.ValueIndex; + newEmbeddedObjectPositionsToValues[objectPosition] = d; + } + var fieldToIndex = new Dictionary(); foreach (var fieldId in fieldIds) { fieldToIndex[fieldNames[fieldId - 1]] = fieldId; } + classInfos[classId] = new SubsystemData.ClassInfo(className, fieldToIndex); } var objectInfos = new Dictionary(); foreach (var objectPosition in objectPositionsToValues.Keys) { - var keyValuePair = objectClasses.First(pair => pair.Value.objectPosition == objectPosition); - objectInfos[keyValuePair.Key] = new SubsystemData.ObjectInfo(objectPosition, objectPositionsToValues[objectPosition]); + var keyValuePair = objectClasses.First(pair => pair.Value.ObjectPosition == objectPosition); + objectInfos[keyValuePair.Key] = new SubsystemData.ObjectInfo(objectPositionsToValues[objectPosition]); + } + + foreach (var objectPosition in embeddedObjectPositionsToValues.Keys) + { + var keyValuePair = objectClasses.First(pair => pair.Value.EmbeddedObjectPosition == objectPosition); + objectInfos[keyValuePair.Key] = + new SubsystemData.ObjectInfo(newEmbeddedObjectPositionsToValues[objectPosition]); } return (classInfos, objectInfos); } - private static Dictionary ReadFieldToFieldDataMapping(BinaryReader reader) - { - var length = reader.ReadInt32(); - var result = new Dictionary(); - for (var i = 0; i < length; i++) - { - var x = reader.ReadInt32(); - var y = reader.ReadInt32(); - var index = x * y; - var link = reader.ReadInt32(); - result[index] = link; - } - return result; - } - - private static Dictionary> ReadObjectPositionsToValuesMapping(BinaryReader reader, int numberOfValues) - { - var result = new Dictionary>(); - reader.ReadBytes(8); - for (var objectPosition = 1; objectPosition <= numberOfValues; objectPosition++) - { - result[objectPosition] = ReadFieldToFieldDataMapping(reader); - var position = reader.BaseStream.Position; - if (position % 8 != 0) - { - reader.ReadBytes(8 - (int)(position % 8)); - } - } - return result; - } - private static Dictionary ReadClassNames( BinaryReader reader, string[] fieldNames, @@ -221,6 +192,98 @@ namespace MatFileHandler return result; } + private static Dictionary ReadEmbeddedObjectPositionsToValuesMapping( + BinaryReader reader, int numberOfObjects) + { + var result = new Dictionary(); + reader.ReadBytes(8); + for (var objectPosition = 1; objectPosition <= numberOfObjects; objectPosition++) + { + var a = reader.ReadInt32(); + var fieldIndex = reader.ReadInt32(); + var c = reader.ReadInt32(); + var valueIndex = reader.ReadInt32(); + result[objectPosition] = new EmbeddedObjectInformation(fieldIndex, valueIndex); + } + + return result; + } + + private static string[] ReadFieldNames(byte[] bytes, int startPosition, int numberOfFields) + { + var result = new string[numberOfFields]; + var position = startPosition; + for (var i = 0; i < numberOfFields; i++) + { + var list = new List(); + while (bytes[position] != 0) + { + list.Add(bytes[position]); + position++; + } + + result[i] = Encoding.ASCII.GetString(list.ToArray()); + position++; + } + + return result; + } + + private static Dictionary ReadFieldToFieldDataMapping(BinaryReader reader) + { + var length = reader.ReadInt32(); + var result = new Dictionary(); + for (var i = 0; i < length; i++) + { + var x = reader.ReadInt32(); + var y = reader.ReadInt32(); + var index = x * y; + var link = reader.ReadInt32(); + result[index] = link; + } + + return result; + } + + private static Dictionary ReadObjectClasses( + BinaryReader reader, + int numberOfObjects) + { + var result = new Dictionary(); + reader.ReadBytes(24); + for (var i = 0; i < numberOfObjects; i++) + { + var classId = reader.ReadInt32(); + reader.ReadBytes(8); + var embeddedObjectPosition = reader.ReadInt32(); + var objectPosition = reader.ReadInt32(); + var loadingOrder = reader.ReadInt32(); + result[i + 1] = + new ObjectClassInformation(embeddedObjectPosition, objectPosition, loadingOrder, classId); + } + + return result; + } + + private static Dictionary> ReadObjectPositionsToValuesMapping( + BinaryReader reader, + int numberOfObjects) + { + var result = new Dictionary>(); + reader.ReadBytes(8); + for (var objectPosition = 1; objectPosition <= numberOfObjects; objectPosition++) + { + result[objectPosition] = ReadFieldToFieldDataMapping(reader); + var position = reader.BaseStream.Position; + if (position % 8 != 0) + { + reader.ReadBytes(8 - (int)(position % 8)); + } + } + + return result; + } + private static (int[] offsets, int newPosition) ReadOffsets(byte[] bytes, int startPosition) { var position = startPosition; @@ -238,29 +301,65 @@ namespace MatFileHandler break; } + offsets.Add(next); } return (offsets.ToArray(), position); } - private static string[] ReadFieldNames(byte[] bytes, int startPosition, int numberOfFields) + private static IArray TransformOpaqueData(IArray array, SubsystemData subsystemData) { - var result = new string[numberOfFields]; - var position = startPosition; - for (var i = 0; i < numberOfFields; i++) + if (array is MatNumericalArrayOf uintArray) { - var list = new List(); - while (bytes[position] != 0) + if (uintArray.Data[0] == 3707764736u) { - list.Add(bytes[position]); - position++; + var (dimensions, indexToObjectId, classIndex) = DataElementReader.ParseOpaqueData(uintArray.Data); + return new OpaqueLink( + uintArray.Name, + string.Empty, + string.Empty, + dimensions, + array as DataElement, + indexToObjectId, + classIndex, + subsystemData); } - result[i] = Encoding.ASCII.GetString(list.ToArray()); - position++; } - return result; + return array; + } + + private struct EmbeddedObjectInformation + { + public EmbeddedObjectInformation(int fieldIndex, int valueIndex) + { + FieldIndex = fieldIndex; + ValueIndex = valueIndex; + } + + public int FieldIndex { get; } + + public int ValueIndex { get; } + } + + private struct ObjectClassInformation + { + public ObjectClassInformation(int embeddedObjectPosition, int objectPosition, int loadingOrder, int classId) + { + EmbeddedObjectPosition = embeddedObjectPosition; + ObjectPosition = objectPosition; + LoadingOrder = loadingOrder; + ClassId = classId; + } + + public int ClassId { get; } + + public int EmbeddedObjectPosition { get; } + + public int LoadingOrder { get; } + + public int ObjectPosition { get; } } } -} +} \ No newline at end of file