diff --git a/README.md b/README.md index 7a39069..526bfe2 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,42 @@ worker.ToBcf(bcf, BcfVersionEnum.Bcf30, outputStream, token); await outputStream.FlushAsync(); ``` +#### Stream processing with delegate callbacks +The converters support processing BCF file streams with delegate-based callback +functions. This allows for the incremental construction of an in-memory BCF +representation, where a delegate function is invoked after each part of the BCF +is processed. It allows a more equal memory usage. + +To use this feature, the user must create a custom delegate class which +implements the `IBcfBuilderDelegate` interface. This interface defines the +callback methods that will be triggered after each part of the BCF is built. + +Implement custom delegate class: +```csharp +public class BcfBuilderDelegate : IBcfBuilderDelegate { + public IBcfBuilderDelegate.OnMarkupCreated + MarkupCreated { get; } = Console.WriteLine; + + public IBcfBuilderDelegate.OnExtensionsCreated + ExtensionsCreated { get; } = Console.WriteLine; + + public IBcfBuilderDelegate.OnProjectCreated + ProjectCreated { get; } = Console.WriteLine; + + public IBcfBuilderDelegate.OnDocumentCreated + DocumentCreatedCreated { get; } = Console.WriteLine; +} +``` + +Process BCF stream: +```csharp +var bcfBuilderDelegate = new BcfBuilderDelegate(); +builder = new BcfBuilder(); +builder.SetDelegate(bcfBuilderDelegate); +await using var stream = new FileStream(source, FileMode.Open, FileAccess.Read); +await builder.ProcessStream(stream); +``` + ## File Structure The structure of the BCF is per [the standard][3]. There is, however, no diff --git a/bcf-toolkit.sln.DotSettings.user b/bcf-toolkit.sln.DotSettings.user index 28cad9c..937d229 100644 --- a/bcf-toolkit.sln.DotSettings.user +++ b/bcf-toolkit.sln.DotSettings.user @@ -1,25 +1,12 @@  /Users/balintbende/Library/Caches/JetBrains/Rider2024.1/resharper-host/temp/Rider/vAny/CoverageData/_bcf-toolkit.-1315391344/Snapshot/snapshot.utdcvr - <SessionState ContinuousTestingMode="0" Name="All tests from Solution #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution #3" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <Solution /> </SessionState> - <SessionState ContinuousTestingMode="0" Name="BuildMaximumInformationBcfFromStreamTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>NUnit3x::8113526D-8A68-4E3E-B4DB-CE235875DDD1::net8.0::tests.Builder.Bcf21.BcfBuilderTests.BuildMaximumInformationBcfFromStreamTest</TestId> - </TestAncestor> -</SessionState> - <SessionState ContinuousTestingMode="0" Name="BuildEmptyBcfFromStream" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>NUnit3x::8113526D-8A68-4E3E-B4DB-CE235875DDD1::net8.0::tests.Builder.Bcf21.BcfBuilderTests.BuildEmptyBcfFromStream</TestId> - </TestAncestor> -</SessionState> @@ -35,9 +22,6 @@ - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> @@ -45,11 +29,25 @@ - <SessionState ContinuousTestingMode="0" IsActive="True" Name="WorkerTests" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>NUnit3x::8113526D-8A68-4E3E-B4DB-CE235875DDD1::net8.0::Tests.WorkerTests</TestId> - </TestAncestor> -</SessionState> + + + + + + + + + + + + + + + + + + + diff --git a/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs b/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs index 7891248..3a22240 100644 --- a/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs @@ -14,9 +14,7 @@ public partial class BcfBuilder : IBcfBuilder< private readonly Bcf _bcf = new(); public BcfBuilder() { - _bcf.Version = new VersionBuilder() - .WithDefaults() - .Build(); + SetVersion(); } public BcfBuilder AddMarkup(Action builder) { @@ -34,6 +32,13 @@ public BcfBuilder SetProject(Action builder) { return this; } + public BcfBuilder SetVersion() { + _bcf.Version = new VersionBuilder() + .WithDefaults() + .Build(); + return this; + } + public BcfBuilder WithDefaults() { this.AddMarkup(m => m.WithDefaults()); return this; diff --git a/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs b/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs index c10555c..11748c7 100644 --- a/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs +++ b/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; using System.IO.Compression; using System.Linq; using System.Threading.Tasks; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Model; using BcfToolkit.Model.Bcf21; using BcfToolkit.Utils; @@ -12,7 +12,27 @@ namespace BcfToolkit.Builder.Bcf21; public partial class BcfBuilder { - public async Task BuildFromStream(Stream source) { + private IBcfBuilderDelegate? _delegate; + + public void SetDelegate(IBcfBuilderDelegate? builderDelegate) { + this._delegate = builderDelegate; + } + + public Task ProcessStream(Stream source) { + if (_delegate is null) { + Console.WriteLine("IBcfBuilderDelegate is not set."); + return Task.CompletedTask; + } + + var tasks = new List { + BcfExtensions.ParseMarkups(source, _delegate.MarkupCreated), + BcfExtensions.ParseProject(source, _delegate.ProjectCreated) + }; + + return Task.WhenAll(tasks); + } + + public async Task BuildInMemoryFromStream(Stream source) { _bcf.Markups = await BcfExtensions.ParseMarkups(source); _bcf.Project = await BcfExtensions.ParseProject(source); diff --git a/src/bcf-toolkit/Builder/Bcf21/VisibilityBuilder.cs b/src/bcf-toolkit/Builder/Bcf21/VisibilityBuilder.cs index 5eeecb8..8e5c3ef 100644 --- a/src/bcf-toolkit/Builder/Bcf21/VisibilityBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf21/VisibilityBuilder.cs @@ -1,6 +1,5 @@ using System; using BcfToolkit.Builder.Bcf21.Interfaces; -using BcfToolkit.Model; using BcfToolkit.Model.Bcf21; using BcfToolkit.Model.Interfaces; diff --git a/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs b/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs index 06cd4b0..18df091 100644 --- a/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs @@ -14,12 +14,11 @@ public partial class BcfBuilder : IBcfBuilder< ExtensionsBuilder, DocumentInfoBuilder>, IDefaultBuilder { + private readonly Bcf _bcf = new(); public BcfBuilder() { - _bcf.Version = new VersionBuilder() - .WithDefaults() - .Build(); + SetVersion(); } public BcfBuilder AddMarkup(Action builder) { @@ -50,6 +49,13 @@ public BcfBuilder SetDocument(Action builder) { return this; } + public BcfBuilder SetVersion() { + _bcf.Version = new VersionBuilder() + .WithDefaults() + .Build(); + return this; + } + public BcfBuilder WithDefaults() { this .AddMarkup(m => m.WithDefaults()) diff --git a/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs b/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs index c0249b7..85bb31a 100644 --- a/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs +++ b/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs @@ -1,22 +1,55 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Model.Bcf30; using BcfToolkit.Utils; namespace BcfToolkit.Builder.Bcf30; public partial class BcfBuilder { + + private IBcfBuilderDelegate? _delegate; + + public void SetDelegate(IBcfBuilderDelegate? builderDelegate) { + this._delegate = builderDelegate; + } + + public Task ProcessStream(Stream source) { + if (_delegate is null) { + Console.WriteLine("IBcfBuilderDelegate is not set."); + return Task.CompletedTask; + } + + var tasks = new List { + BcfExtensions.ParseMarkups( + source, + _delegate.MarkupCreated), + BcfExtensions.ParseExtensions( + source, + _delegate.ExtensionsCreated), + BcfExtensions.ParseProject( + source, + _delegate.ProjectCreated), + BcfExtensions.ParseDocuments( + source, + _delegate.DocumentCreatedCreated) + }; + + return Task.WhenAll(tasks); + } + + // /// /// The method builds and returns an instance of BCF 3.0 object from the /// specified file stream. /// /// The file stream. /// Returns the built object. - public async Task BuildFromStream(Stream source) { - _bcf.Markups = - await BcfExtensions.ParseMarkups(source); + public async Task BuildInMemoryFromStream(Stream source) { + _bcf.Markups = await BcfExtensions.ParseMarkups(source); _bcf.Extensions = await BcfExtensions.ParseExtensions(source); _bcf.Project = await BcfExtensions.ParseProject(source); _bcf.Document = await BcfExtensions.ParseDocuments(source); diff --git a/src/bcf-toolkit/Builder/Bcf30/Interfaces/IBcfBuilder.cs b/src/bcf-toolkit/Builder/Bcf30/Interfaces/IBcfBuilder.cs index 795e73f..c80b33c 100644 --- a/src/bcf-toolkit/Builder/Bcf30/Interfaces/IBcfBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf30/Interfaces/IBcfBuilder.cs @@ -12,6 +12,7 @@ public interface IBcfBuilder< out TDocumentInfoBuilder> : IBuilder, IFromStreamBuilder { + /// /// Returns the builder object extended with a new `Markup`. /// diff --git a/src/bcf-toolkit/Builder/Bcf30/ViewPointBuilder.cs b/src/bcf-toolkit/Builder/Bcf30/ViewPointBuilder.cs index c92c444..f366b21 100644 --- a/src/bcf-toolkit/Builder/Bcf30/ViewPointBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf30/ViewPointBuilder.cs @@ -39,6 +39,7 @@ public ViewPointBuilder SetGuid(string guid) { return this; } + public ViewPointBuilder SetSnapshotData(FileData? snapshotData) { _viewPoint.SnapshotData = snapshotData; return this; diff --git a/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs b/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs new file mode 100644 index 0000000..a86ea1a --- /dev/null +++ b/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs @@ -0,0 +1,25 @@ +using BcfToolkit.Model.Interfaces; + +namespace BcfToolkit.Builder.Interfaces; + +public interface IBcfBuilderDelegate { + public delegate void OnMarkupCreated(TMarkup markup) + where TMarkup : IMarkup; + + public delegate void OnProjectCreated( + TProjectInfo projectInfo) + where TProjectInfo : IProject; + + public delegate void OnExtensionsCreated( + TExtensions extensions) + where TExtensions : IExtensions; + + public delegate void OnDocumentCreated( + TDocumentInfo documentInfo) + where TDocumentInfo : IDocumentInfo; + + public OnMarkupCreated MarkupCreated { get; } + public OnExtensionsCreated ExtensionsCreated { get; } + public OnProjectCreated ProjectCreated { get; } + public OnDocumentCreated DocumentCreatedCreated { get; } +} \ No newline at end of file diff --git a/src/bcf-toolkit/Builder/Interfaces/IFromStreamBuilder.cs b/src/bcf-toolkit/Builder/Interfaces/IFromStreamBuilder.cs index b349d16..9c89cd8 100644 --- a/src/bcf-toolkit/Builder/Interfaces/IFromStreamBuilder.cs +++ b/src/bcf-toolkit/Builder/Interfaces/IFromStreamBuilder.cs @@ -8,5 +8,5 @@ public interface IFromStreamBuilder { /// It builds and returns the specified item from a specified stream. /// /// Returns the built item. - Task BuildFromStream(Stream source); + Task BuildInMemoryFromStream(Stream source); } \ No newline at end of file diff --git a/src/bcf-toolkit/Converter/Bcf21/Converter.cs b/src/bcf-toolkit/Converter/Bcf21/Converter.cs index 8205637..a9066a8 100644 --- a/src/bcf-toolkit/Converter/Bcf21/Converter.cs +++ b/src/bcf-toolkit/Converter/Bcf21/Converter.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using BcfToolkit.Builder.Bcf21; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Utils; using BcfToolkit.Model; using BcfToolkit.Model.Bcf21; @@ -57,7 +58,7 @@ private readonly Dictionary< }; public async Task BcfToJson(Stream source, string targetPath) { - var bcf = await _builder.BuildFromStream(source); + var bcf = await _builder.BuildInMemoryFromStream(source); await FileWriter.WriteBcfToJson(bcf, targetPath); } @@ -146,9 +147,16 @@ public Task ToJson(IBcf bcf, string target) { } public async Task BcfFromStream(Stream stream) { - var bcf = await _builder.BuildFromStream(stream); + var bcf = await _builder.BuildInMemoryFromStream(stream); var targetVersion = BcfVersion.TryParse(typeof(T)); var converterFn = _converterFn[targetVersion]; return (T)converterFn(bcf); } + + public Task ProcessStream( + Stream stream, + IBcfBuilderDelegate builderDelegate) { + _builder.SetDelegate(builderDelegate); + return _builder.ProcessStream(stream); + } } \ No newline at end of file diff --git a/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs b/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs index ba41786..ed804b1 100644 --- a/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs +++ b/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs @@ -2,11 +2,23 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using BcfToolkit.Builder.Bcf30; using BcfToolkit.Utils; namespace BcfToolkit.Converter.Bcf21; +// public class BcfBuilderDelegate : BcfToolkit.Builder.Bcf21.Interfaces.IBcfBuilderDelegate { +// public BcfToolkit.Builder.Bcf21.Interfaces.IBcfBuilderDelegate.OnMarkupCreated +// MarkupCreated { get; } = markup => { +// var converted = SchemaConverterToBcf30.ConvertMarkup(markup); +// }; +// +// public BcfToolkit.Builder.Bcf21.Interfaces.IBcfBuilderDelegate.OnProjectCreated +// ProjectCreated { get; } = Console.WriteLine; +// +// } + public static class SchemaConverterToBcf30 { /// @@ -43,7 +55,13 @@ public static Model.Bcf30.Bcf Convert(Model.Bcf21.Bcf from) { return builder.Build(); } - private static Model.Bcf30.Markup ConvertMarkup(Model.Bcf21.Markup from) { + // public static async Task ProcessStream(Stream stream) { + // var del = new BcfBuilderDelegate(); + // var fromBuilder = new BcfToolkit.Builder.Bcf21.BcfBuilder(del); + // await fromBuilder.ProcessStream(stream); + // } + + public static Model.Bcf30.Markup ConvertMarkup(Model.Bcf21.Markup from) { var builder = new MarkupBuilder(); var topic = from.Topic; builder diff --git a/src/bcf-toolkit/Converter/Bcf30/Converter.cs b/src/bcf-toolkit/Converter/Bcf30/Converter.cs index 8efe122..f16914e 100644 --- a/src/bcf-toolkit/Converter/Bcf30/Converter.cs +++ b/src/bcf-toolkit/Converter/Bcf30/Converter.cs @@ -6,11 +6,11 @@ using System.Threading; using System.Threading.Tasks; using BcfToolkit.Builder.Bcf30; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Utils; using BcfToolkit.Model; using BcfToolkit.Model.Bcf30; using BcfToolkit.Model.Interfaces; -using Version = BcfToolkit.Model.Bcf30.Version; namespace BcfToolkit.Converter.Bcf30; @@ -59,7 +59,7 @@ private readonly Dictionary< public async Task BcfToJson(Stream source, string target) { var builder = new BcfBuilder(); - var bcf = await builder.BuildFromStream(source); + var bcf = await builder.BuildInMemoryFromStream(source); await FileWriter.WriteJson(bcf, target); } @@ -141,9 +141,16 @@ public Task ToJson(IBcf bcf, string target) { } public async Task BcfFromStream(Stream stream) { - var bcf = await _builder.BuildFromStream(stream); + var bcf = await _builder.BuildInMemoryFromStream(stream); var targetVersion = BcfVersion.TryParse(typeof(T)); var converterFn = _converterFn[targetVersion]; return (T)converterFn(bcf); } + + public Task ProcessStream( + Stream stream, + IBcfBuilderDelegate builderDelegate) { + _builder.SetDelegate(builderDelegate); + return _builder.ProcessStream(stream); + } } \ No newline at end of file diff --git a/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs b/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs index d089699..022fddd 100644 --- a/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs +++ b/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs @@ -126,7 +126,6 @@ public static void SerializeAndWriteBcfToStream(IBcf bcf, ZipArchive zip, } var topicFolder = $"{guid}"; - zip.CreateEntryFromObject($"{topicFolder}/markup.bcf", markup); foreach (var viewpoint in markup.Topic.Viewpoints) { diff --git a/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs b/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs index 7625438..6c93cb2 100644 --- a/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs +++ b/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs @@ -205,11 +205,21 @@ private static Model.Bcf21.ViewPoint ConvertViewPoint( var builder = new PerspectiveCameraBuilder(); return builder - .SetCameraDirection(from.CameraDirection.X, from.CameraDirection.Y, from.CameraDirection.Z) - .SetCameraViewPoint(from.CameraViewPoint.X, from.CameraViewPoint.Y, from.CameraViewPoint.Z) - .SetCameraUpVector(from.CameraUpVector.X, from.CameraUpVector.Y, from.CameraUpVector.Z) + .SetCameraDirection( + from.CameraDirection.X, + from.CameraDirection.Y, + from.CameraDirection.Z) + .SetCameraViewPoint( + from.CameraViewPoint.X, + from.CameraViewPoint.Y, + from.CameraViewPoint.Z) + .SetCameraUpVector( + from.CameraUpVector.X, + from.CameraUpVector.Y, + from.CameraUpVector.Z) .SetFieldOfView(from.FieldOfView) .Build(); + //TODO: AspectRatio data loss } diff --git a/src/bcf-toolkit/Converter/IConverter.cs b/src/bcf-toolkit/Converter/IConverter.cs index 20bddd7..5b9ca2e 100644 --- a/src/bcf-toolkit/Converter/IConverter.cs +++ b/src/bcf-toolkit/Converter/IConverter.cs @@ -2,6 +2,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Model; using BcfToolkit.Model.Interfaces; @@ -135,4 +136,17 @@ void ToBcf(IBcf bcf, BcfVersionEnum targetVersion, Stream stream, /// Returns the `Bcf` object which is specified as a type parameter. /// Task BcfFromStream(Stream stream); + + /// + /// The function processes BCF file stream and constructs an in-memory + /// representation of the BCF. As each part of the BCF is built the + /// associated delegate function is invoked. + /// + /// The BCF file stream. + /// + /// The delegate object containing callback functions that are invoked + /// after each part of the BCF is constructed. + /// + /// + public Task ProcessStream(Stream stream, IBcfBuilderDelegate builderDelegate); } \ No newline at end of file diff --git a/src/bcf-toolkit/Model/Bcf21/VisInfo.cs b/src/bcf-toolkit/Model/Bcf21/VisInfo.cs index ad07be7..39199f6 100644 --- a/src/bcf-toolkit/Model/Bcf21/VisInfo.cs +++ b/src/bcf-toolkit/Model/Bcf21/VisInfo.cs @@ -114,6 +114,8 @@ public partial class PerspectiveCamera [System.ComponentModel.DescriptionAttribute("It is currently limited to a value between 45 and 60 degrees. This limitation wil" + "l be dropped in the next release and viewers should be expect values outside thi" + "s range in current implementations.")] + + // NOTIFICATION: limitation does not really make sense // [System.ComponentModel.DataAnnotations.RangeAttribute(typeof(double), "45", "60")] [System.ComponentModel.DataAnnotations.RequiredAttribute(AllowEmptyStrings=true)] [System.Xml.Serialization.XmlElementAttribute("FieldOfView")] @@ -619,10 +621,12 @@ public partial class VisualizationInfo public Components Components { get; set; } [System.Xml.Serialization.XmlElementAttribute("OrthogonalCamera")] - public OrthogonalCamera OrthogonalCamera { get; set; } +#nullable enable + public OrthogonalCamera? OrthogonalCamera { get; set; } [System.Xml.Serialization.XmlElementAttribute("PerspectiveCamera")] - public PerspectiveCamera PerspectiveCamera { get; set; } + public PerspectiveCamera? PerspectiveCamera { get; set; } +#nullable disable [System.Xml.Serialization.XmlIgnoreAttribute()] private System.Collections.ObjectModel.Collection _lines; diff --git a/src/bcf-toolkit/Model/Bcf30/Visinfo.cs b/src/bcf-toolkit/Model/Bcf30/Visinfo.cs index 227fd18..86f2d25 100644 --- a/src/bcf-toolkit/Model/Bcf30/Visinfo.cs +++ b/src/bcf-toolkit/Model/Bcf30/Visinfo.cs @@ -684,11 +684,13 @@ public partial class VisualizationInfo [System.Xml.Serialization.XmlElementAttribute("Components")] public Components Components { get; set; } +#nullable enable [System.Xml.Serialization.XmlElementAttribute("OrthogonalCamera")] - public OrthogonalCamera OrthogonalCamera { get; set; } + public OrthogonalCamera? OrthogonalCamera { get; set; } [System.Xml.Serialization.XmlElementAttribute("PerspectiveCamera")] - public PerspectiveCamera PerspectiveCamera { get; set; } + public PerspectiveCamera? PerspectiveCamera { get; set; } +#nullable disable [System.Xml.Serialization.XmlIgnoreAttribute()] private System.Collections.ObjectModel.Collection _lines; diff --git a/src/bcf-toolkit/Program.cs b/src/bcf-toolkit/Program.cs index 89c80a8..48b0e62 100644 --- a/src/bcf-toolkit/Program.cs +++ b/src/bcf-toolkit/Program.cs @@ -22,7 +22,6 @@ private static async Task HandleArguments(string[] args) { Log.Configure(Serilog.Log.Debug, null, null, Serilog.Log.Error); - var sourcePathOption = new Option( name: "--source", description: "The absolute path of the source file.") { IsRequired = true }; @@ -60,7 +59,6 @@ private static async Task DoRootCommand(InputArguments arguments) { Log.Error(e.Message); Environment.Exit(9); } - Environment.Exit(0); } } \ No newline at end of file diff --git a/src/bcf-toolkit/Utils/BcfExtensions.cs b/src/bcf-toolkit/Utils/BcfExtensions.cs index 3e1ac8f..5aa2718 100644 --- a/src/bcf-toolkit/Utils/BcfExtensions.cs +++ b/src/bcf-toolkit/Utils/BcfExtensions.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using System.Xml.Linq; using System.Xml.Serialization; +using BcfToolkit.Builder.Interfaces; using BcfToolkit.Model; using BcfToolkit.Model.Interfaces; @@ -24,15 +25,25 @@ public static class BcfExtensions { /// The method unzips the BCFzip from a stream, /// and parses the markup xml files within to create an in memory /// representation of the data. - /// Topic folder structure inside a BCFzip archive: - /// The folder name is the GUID of the topic. This GUID is in the UUID form. - /// The GUID must be all-lowercase. The folder contains the following file: - /// * markup.bcf - /// Additionally the folder can contain other files: - /// * Viewpoint files - /// * Snapshot files - /// * Bitmaps - /// Notification: This function adjusts the stream position back to 0 in order to use it again. + /// + /// The source stream of the BCFzip. + /// + /// If the delegate function is set, the caller will receive every markup + /// as they are created, without building up an in-memory representation + /// of the entire BCF document. + /// + public static async Task ParseMarkups(Stream stream, + IBcfBuilderDelegate.OnMarkupCreated onMarkupCreated) + where TMarkup : IMarkup + where TVisualizationInfo : IVisualizationInfo { + await _ParseMarkups(stream, onMarkupCreated); + } + + /// + /// The method unzips the BCFzip from a stream, + /// and parses the markup xml files within to create an in memory + /// representation of the data. /// /// The source stream of the BCFzip. /// Returns a Task with a List of `Markup` models. @@ -41,7 +52,34 @@ public static async Task> ParseMarkups< TVisualizationInfo>(Stream stream) where TMarkup : IMarkup where TVisualizationInfo : IVisualizationInfo { - if (stream == null || !stream.CanRead) + return await _ParseMarkups(stream); + } + + /// + /// The method unzips the BCFzip from a stream, + /// and parses the markup xml files within to create an in memory + /// representation of the data. + /// + /// The source stream of the BCFzip. + /// + /// If the delegate function is set, the caller will receive every markup + /// as they are created, without building up an in-memory representation + /// of the entire BCF document. + /// + /// + /// + /// + /// + /// Exception is thrown if the source stream is not readable. + /// + private static async Task> _ParseMarkups< + TMarkup, + TVisualizationInfo>( + Stream stream, + IBcfBuilderDelegate.OnMarkupCreated? onMarkupCreated = null) + where TMarkup : IMarkup + where TVisualizationInfo : IVisualizationInfo { + if (stream is not { CanRead: true }) throw new ArgumentException("Source stream is not readable."); var objType = typeof(TMarkup); @@ -56,7 +94,7 @@ public static async Task> ParseMarkups< // This iterates through the archive file-by-file and the sub-folders // being just in the names of the entries. // We know it is a new Markup, when the folder (uuid) changes. In that - // case the Markup object is created and pushed into the bac. A special + // case the Markup object is created and pushed into the bag. A special // case is the last entry in the archive, when that is reached, the // markup is created as well. @@ -82,18 +120,18 @@ public static async Task> ParseMarkups< // This sets the folder context var uuid = entry.FullName.Split("/")[0]; - //var isTopic = Regex.IsMatch(uuid.Replace("-", ""), "^[a-fA-F0-9]+$"); var isNewTopic = !string.IsNullOrEmpty(currentUuid) && uuid != currentUuid; if (isNewTopic) - WritingOutMarkup( + AddOrInvokeMarkup( ref markup, ref visInfos, ref snapshots, currentUuid, - markups); + markups, + onMarkupCreated); currentUuid = uuid; @@ -124,12 +162,13 @@ public static async Task> ParseMarkups< } if (isLastTopicEntry) - WritingOutMarkup( + AddOrInvokeMarkup( ref markup, ref visInfos, ref snapshots, currentUuid, - markups); + markups, + onMarkupCreated); } // Stream must be positioned back to 0 in order to use it again @@ -137,18 +176,36 @@ public static async Task> ParseMarkups< return markups; } - private static void WritingOutMarkup( + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static void AddOrInvokeMarkup( ref TMarkup? markup, ref Dictionary? visInfos, ref Dictionary? snapshots, string currentUuid, - ConcurrentBag markups) + ConcurrentBag markups, + IBcfBuilderDelegate.OnMarkupCreated? onMarkupCreated = null) where TMarkup : IMarkup where TVisualizationInfo : IVisualizationInfo { // This is a new subfolder, writing out Markup. if (markup != null) { markup.SetViewPoints(visInfos, snapshots); - markups.Add(markup); + + // If a delegate is provided, invoke it without adding markup to the BCF + if (onMarkupCreated is not null) + onMarkupCreated?.Invoke(markup); + else + markups.Add(markup); // Null-ing external references markup = default; @@ -171,8 +228,41 @@ private static void WritingOutMarkup( /// /// The file stream of the BCFzip. /// Returns a Task with an `Extensions` model. - public static Task ParseExtensions(Stream stream) { - return ParseRequired(stream, entry => entry.IsExtensions()); + public static Task ParseExtensions(Stream stream) + where TExtensions : IExtensions { + return _ParseExtensions(stream); + } + + /// + /// The method unzips the BCFzip from a file stream, + /// and parses the `extensions.xml` file within to create an in memory + /// representation of the data. + /// This is a required in the BCF archive. + /// HISTORY: New file in BCF 3.0. + /// An XML file defining the extensions of a project. + /// + /// The file stream of the BCFzip. + /// + /// If the delegate function is set, the caller will receive an extensions + /// as it is created, without building up an in-memory representation + /// of the entire BCF document. + /// + /// Returns a Task with an `Extensions` model. + public static Task ParseExtensions( + Stream stream, + IBcfBuilderDelegate.OnExtensionsCreated? onExtensionsCreated) + where TExtensions : IExtensions { + return _ParseExtensions(stream, onExtensionsCreated); + } + + private static async Task _ParseExtensions( + Stream stream, + IBcfBuilderDelegate.OnExtensionsCreated? onExtensionsCreated = null) + where TExtensions : IExtensions { + var extensions = + await ParseRequired(stream, entry => entry.IsExtensions()); + onExtensionsCreated?.Invoke(extensions); + return extensions; } /// @@ -186,8 +276,55 @@ public static Task ParseExtensions(Stream stream) { /// /// The stream of the BCFzip. /// Returns a Task with an `ProjectInfo` model. - public static Task ParseProject(Stream stream) { - return ParseOptional(stream, entry => entry.IsBcfProject()); + public static Task ParseProject(Stream stream) + where TProjectInfo : IProject { + return _ParseProject(stream); + } + + /// + /// The method unzips the BCFzip from a file stream, + /// and parses the `project.bcfp` file within to create an in memory + /// representation of the data. + /// This is an optional file in the BCF archive. + /// HISTORY: From BCF 2.0. + /// The project file contains reference information about the project + /// the topics belong to. + /// + /// The stream of the BCFzip. + /// + /// If the delegate function is set, the caller will receive a project + /// as it is created, without building up an in-memory representation + /// of the entire BCF document. + /// + /// Returns a Task with an `ProjectInfo` model. + public static Task ParseProject( + Stream stream, + IBcfBuilderDelegate.OnProjectCreated onProjectCreated) + where TProjectInfo : IProject { + return _ParseProject(stream, onProjectCreated); + } + + private static async Task _ParseProject( + Stream stream, + IBcfBuilderDelegate.OnProjectCreated? onProjectCreated = null) + where TProjectInfo : IProject { + var project = await ParseOptional(stream, entry => entry.IsBcfProject()); + if (project is not null) + onProjectCreated?.Invoke(project); + return project; + } + + public static Task ParseDocuments( + Stream stream) + where TDocumentInfo : IDocumentInfo { + return _ParseDocuments(stream); + } + + public static Task ParseDocuments( + Stream stream, + IBcfBuilderDelegate.OnDocumentCreated? onDocumentCreated) + where TDocumentInfo : IDocumentInfo { + return _ParseDocuments(stream, onDocumentCreated); } /// @@ -201,13 +338,15 @@ public static Task ParseExtensions(Stream stream) { /// document guid. The actual filename is stored in the documents.xml. /// /// The `documents.xml` and documents folder are optional in the BCF archive. - /// + /// /// HISTORY: New in BCF 3.0. /// /// The stream of to the BCFzip. + /// /// Returns a Task with an `DocumentInfo` model. - public static async Task - ParseDocuments(Stream stream) + private static async Task _ParseDocuments( + Stream stream, + IBcfBuilderDelegate.OnDocumentCreated? onDocumentCreated = null) where TDocumentInfo : IDocumentInfo { if (stream is null || !stream.CanRead) throw new ArgumentException("Source stream is not readable."); @@ -239,13 +378,14 @@ public static async Task // Stream must be positioned back to 0 in order to use it again stream.Position = 0; + onDocumentCreated?.Invoke(documentInfo); return documentInfo; } - private static Task ParseRequired( + private static async Task ParseRequired( Stream stream, Func filterFn) { - var obj = ParseObject(stream, filterFn); + var obj = await ParseObject(stream, filterFn); if (obj is null) throw new InvalidDataException($"{typeof(T)} is not found in BCF."); return obj; diff --git a/src/bcf-toolkit/Utils/ZipArchiveExtensions.cs b/src/bcf-toolkit/Utils/ZipArchiveExtensions.cs index 2d4de4b..e2098f5 100644 --- a/src/bcf-toolkit/Utils/ZipArchiveExtensions.cs +++ b/src/bcf-toolkit/Utils/ZipArchiveExtensions.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; using System.IO; using System.IO.Compression; +using System.Collections.Generic; using System.Linq; using System.Xml.Serialization; diff --git a/src/tests/Builder/Bcf21/BcfBuilderTests.cs b/src/tests/Builder/Bcf21/BcfBuilderInMemoryTests.cs similarity index 97% rename from src/tests/Builder/Bcf21/BcfBuilderTests.cs rename to src/tests/Builder/Bcf21/BcfBuilderInMemoryTests.cs index 855845b..eadb88b 100644 --- a/src/tests/Builder/Bcf21/BcfBuilderTests.cs +++ b/src/tests/Builder/Bcf21/BcfBuilderInMemoryTests.cs @@ -9,9 +9,9 @@ using BcfToolkit.Model.Bcf21; using NUnit.Framework; -namespace tests.Builder.Bcf21; +namespace Tests.Builder.Bcf21; -public class BcfBuilderTests { +public class BcfBuilderInMemoryTests { private BcfBuilder _builder = null!; @@ -153,7 +153,7 @@ public async Task BuildMaximumInformationBcfFromStreamTest() { "Resources/Bcf/v2.1/MaximumInformation.bcfzip", FileMode.Open, FileAccess.Read); - var bcf = await _builder.BuildFromStream(stream); + var bcf = await _builder.BuildInMemoryFromStream(stream); Assert.That(bcf.Markups.Count, Is.EqualTo(2)); var markup = bcf .Markups diff --git a/src/tests/Builder/Bcf30/BcfBuilderTests.cs b/src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs similarity index 72% rename from src/tests/Builder/Bcf30/BcfBuilderTests.cs rename to src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs index 81385db..d7d113c 100644 --- a/src/tests/Builder/Bcf30/BcfBuilderTests.cs +++ b/src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs @@ -4,19 +4,19 @@ using BcfToolkit.Builder.Bcf30; using NUnit.Framework; -namespace tests.Builder.Bcf30; +namespace Tests.Builder.Bcf30; -public class BcfBuilderTests { - private BcfBuilder _builder = null!; +public class BcfBuilderInMemoryTests { + private BcfBuilder _inMemoryBuilder = null!; [SetUp] public void Setup() { - _builder = new BcfBuilder(); + _inMemoryBuilder = new BcfBuilder(); } [Test] public void BuildBcfWithComplexFields() { - var bcf = _builder + var bcf = _inMemoryBuilder .AddMarkup(m => m .SetTitle("Title") .SetGuid("3ffb4df2-0187-49a9-8a4a-23992696bafd") @@ -38,27 +38,29 @@ public void BuildBcfWithComplexFields() { [Test] public void BuildBcfWithMissingRequiredFields() { - Assert.That(() => _builder.Build(), Throws.ArgumentException); + Assert.That(() => _inMemoryBuilder.Build(), Throws.ArgumentException); } [Test] - public async Task BuildBcfFromStream() { + public async Task BuildInMemoryBcfFromStream() { await using var stream = new FileStream( "Resources/Bcf/v3.0/UserAssignment.bcfzip", FileMode.Open, FileAccess.Read); - var bcf = await _builder.BuildFromStream(stream); + var bcf = await _inMemoryBuilder.BuildInMemoryFromStream(stream); Assert.That(1, Is.EqualTo(bcf.Markups.Count)); Assert.That( "Architect@example.com", Is.EqualTo(bcf.Markups.FirstOrDefault()?.Topic.AssignedTo)); } + [Test] - public async Task BuildEmptyBcfFromStream() { + public async Task BuildEmptyBcfFromStreamTest() { await using var stream = new FileStream( "Resources/Bcf/v3.0/Empty.bcfzip", FileMode.Open, FileAccess.Read); - Assert.That(() => _builder.BuildFromStream(stream), Throws.ArgumentException); + Assert.That(() => _inMemoryBuilder.BuildInMemoryFromStream(stream), + Throws.Exception); } } \ No newline at end of file diff --git a/src/tests/Builder/Bcf30/BcfBuilderStreamTest.cs b/src/tests/Builder/Bcf30/BcfBuilderStreamTest.cs new file mode 100644 index 0000000..5fa4668 --- /dev/null +++ b/src/tests/Builder/Bcf30/BcfBuilderStreamTest.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using BcfToolkit.Builder.Bcf30; +using BcfToolkit.Builder.Interfaces; +using BcfToolkit.Model.Bcf30; +using BcfToolkit.Model.Interfaces; +using NUnit.Framework; + +namespace Tests.Builder.Bcf30; + +public class BcfBuilderDelegate : IBcfBuilderDelegate { + public IBcfBuilderDelegate.OnMarkupCreated + MarkupCreated { get; } = m => { + var markup = (Markup)m; + Console.WriteLine(markup.Topic.Guid); + }; + + public IBcfBuilderDelegate.OnExtensionsCreated + ExtensionsCreated { get; } = Console.WriteLine; + + public IBcfBuilderDelegate.OnProjectCreated + ProjectCreated { get; } = Console.WriteLine; + + public IBcfBuilderDelegate.OnDocumentCreated + DocumentCreatedCreated { get; } = Console.WriteLine; +} + +public class BcfBuilderStreamTest { + private BcfBuilder _streamBuilder = null!; + + [SetUp] + public void Setup() { + var bcfBuilderDelegate = new BcfBuilderDelegate(); + _streamBuilder = new BcfBuilder(); + _streamBuilder.SetDelegate(bcfBuilderDelegate); + } + + [Test] + public async Task ProcessBcfStreamTest() { + await using var stream = new FileStream( + "Resources/Bcf/v3.0/UserAssignment.bcfzip", + FileMode.Open, + FileAccess.Read); + + await _streamBuilder.ProcessStream(stream); + } +} \ No newline at end of file diff --git a/src/tests/Builder/MarkupBuilderTests.cs b/src/tests/Builder/MarkupBuilderTests.cs index db2e6cc..576f5b9 100644 --- a/src/tests/Builder/MarkupBuilderTests.cs +++ b/src/tests/Builder/MarkupBuilderTests.cs @@ -38,7 +38,7 @@ public void BuildMarkupWithRequiredFields() { .Build(); Assert.That( "3ffb4df2-0187-49a9-8a4a-23992696bafd", - Is.EqualTo(markup.GetTopic()!.Guid)); + Is.EqualTo(markup.GetTopic().Guid)); } [Test] diff --git a/src/tests/Converter/Bcf21/ConverterTests.cs b/src/tests/Converter/Bcf21/ConverterTests.cs index 4faa3d7..2ed276f 100644 --- a/src/tests/Converter/Bcf21/ConverterTests.cs +++ b/src/tests/Converter/Bcf21/ConverterTests.cs @@ -165,9 +165,9 @@ public async Task BuildSimpleV21BcfFromStreamTest() { FileMode.Open, FileAccess.Read); var bcf = await _converter.BcfFromStream(stream); - Assert.That(typeof(Bcf), Is.EqualTo(bcf.GetType())); - Assert.That(1, Is.EqualTo(bcf.Markups.Count)); - Assert.That("2.1", Is.EqualTo(bcf.Version?.VersionId)); + Assert.That(bcf.GetType(), Is.EqualTo(typeof(Bcf))); + Assert.That(bcf.Markups.Count, Is.EqualTo(1)); + Assert.That(bcf.Version?.VersionId, Is.EqualTo("2.1")); } /// diff --git a/src/tests/Converter/Bcf21/FileWriterTest.cs b/src/tests/Converter/Bcf21/FileWriterTest.cs index 29d5742..8532d7d 100644 --- a/src/tests/Converter/Bcf21/FileWriterTest.cs +++ b/src/tests/Converter/Bcf21/FileWriterTest.cs @@ -19,8 +19,7 @@ public async Task WriteBcfWithCreatingZipEntryFromStreamTest() { var bcfResultBuilder = new BcfBuilder(); var bcfResult = await bcfResultBuilder - .BuildFromStream(stream); - + .BuildInMemoryFromStream(stream); Assert.That( bcf.Markups.FirstOrDefault()?.Topic.Title, Is.EqualTo(bcfResult.Markups.FirstOrDefault()?.Topic.Title)); @@ -37,8 +36,7 @@ public async Task WriteBcfWithSavingXmlTmpTest() { var bcfResultBuilder = new BcfBuilder(); var bcfResult = await bcfResultBuilder - .BuildFromStream(stream); - + .BuildInMemoryFromStream(stream); Assert.That( bcf.Markups.FirstOrDefault()?.Topic.Title, Is.EqualTo(bcfResult.Markups.FirstOrDefault()?.Topic.Title)); diff --git a/src/tests/Converter/Bcf30/ConverterTests.cs b/src/tests/Converter/Bcf30/ConverterTests.cs index d5542fa..1e6ac89 100644 --- a/src/tests/Converter/Bcf30/ConverterTests.cs +++ b/src/tests/Converter/Bcf30/ConverterTests.cs @@ -31,9 +31,9 @@ public void BcfToJsonSampleFilesTest() { _converter.BcfToJson( "Resources/Bcf/v3.0/DocumentReferenceExternal.bcfzip", "Resources/output/json/v3.0/DocumentReferenceExternal"); - // _converter.BcfToJson( - // "Resources/Bcf/v3.0/DocumentReferenceInternal.bcfzip", - // "Resources/output/json/v3.0/DocumentReferenceInternal"); + _converter.BcfToJson( + "Resources/Bcf/v3.0/DocumentReferenceInternal.bcfzip", + "Resources/output/json/v3.0/DocumentReferenceInternal"); _converter.BcfToJson( "Resources/Bcf/v3.0/DueDate.bcfzip", "Resources/output/json/v3.0/DueDate"); @@ -49,6 +49,9 @@ public void BcfToJsonSampleFilesTest() { _converter.BcfToJson( "Resources/Bcf/v3.0/SingleInvisibleWall.bcfzip", "Resources/output/json/v3.0/SingleInvisibleWall"); + _converter.BcfToJson( + "Resources/Bcf/v3.0/SingleVisibleWall.bcfzip", + "Resources/output/json/v3.0/SingleVisibleWall"); _converter.BcfToJson( "Resources/Bcf/v3.0/TestBcf30.bcfzip", "Resources/output/json/v3.0/TestBcf30"); diff --git a/src/tests/Converter/Bcf30/FileWriterTests.cs b/src/tests/Converter/Bcf30/FileWriterTests.cs index f95afd4..0df62ec 100644 --- a/src/tests/Converter/Bcf30/FileWriterTests.cs +++ b/src/tests/Converter/Bcf30/FileWriterTests.cs @@ -29,8 +29,7 @@ public async Task WriteBcfWithCreatingZipEntryFromStreamTest() { var stream = await FileWriter.SerializeAndWriteBcf(bcf); var bcfResultBuilder = new BcfBuilder(); - var bcfResult = await bcfResultBuilder - .BuildFromStream(stream); + var bcfResult = await bcfResultBuilder.BuildInMemoryFromStream(stream); Assert.That( bcf.Markups.FirstOrDefault()?.Topic.Title, @@ -51,7 +50,7 @@ public async Task WriteBcfWithSavingXmlTmpTest() { var bcfResultBuilder = new BcfBuilder(); var bcfResult = await bcfResultBuilder - .BuildFromStream(stream); + .BuildInMemoryFromStream(stream); Assert.That( bcf.Markups.FirstOrDefault()?.Topic.Title, diff --git a/src/tests/Resources/Bcf/v3.0/SingleVisibleWall.bcfzip b/src/tests/Resources/Bcf/v3.0/SingleVisibleWall.bcfzip new file mode 100644 index 0000000..e497b83 Binary files /dev/null and b/src/tests/Resources/Bcf/v3.0/SingleVisibleWall.bcfzip differ diff --git a/src/tests/Resources/Json/v2.1/AllPartsVisible/imagebase64.txt b/src/tests/Resources/Json/v2.1/AllPartsVisible/imagebase64.txt new file mode 100644 index 0000000..cac630e --- /dev/null +++ b/src/tests/Resources/Json/v2.1/AllPartsVisible/imagebase64.txt @@ -0,0 +1 @@ +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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/src/tests/Utils/BcfExtensionsTests.cs b/src/tests/Utils/BcfExtensionsTests.cs index 7483edd..fafb933 100644 --- a/src/tests/Utils/BcfExtensionsTests.cs +++ b/src/tests/Utils/BcfExtensionsTests.cs @@ -7,7 +7,7 @@ using bcf21 = BcfToolkit.Model.Bcf21; using bcf30 = BcfToolkit.Model.Bcf30; -namespace tests.Utils; +namespace Tests.Utils; [TestFixture] public class BcfExtensionsTests { diff --git a/src/tests/Utils/JsonExtensionsTests.cs b/src/tests/Utils/JsonExtensionsTests.cs index 0b1c281..5c0da3b 100644 --- a/src/tests/Utils/JsonExtensionsTests.cs +++ b/src/tests/Utils/JsonExtensionsTests.cs @@ -5,7 +5,7 @@ using bcf21 = BcfToolkit.Model.Bcf21; using bcf30 = BcfToolkit.Model.Bcf30; -namespace tests.Utils; +namespace Tests.Utils; public class JsonExtensionsTests { /// diff --git a/src/tests/Utils/ZipArchiveEntryExtensionsTests.cs b/src/tests/Utils/ZipArchiveEntryExtensionsTests.cs index d47a9d6..0e413b5 100644 --- a/src/tests/Utils/ZipArchiveEntryExtensionsTests.cs +++ b/src/tests/Utils/ZipArchiveEntryExtensionsTests.cs @@ -5,7 +5,7 @@ using BcfToolkit.Utils; using NUnit.Framework; -namespace tests.Utils; +namespace Tests.Utils; [TestFixture] public class ZipArchiveEntryExtensionsTests { diff --git a/src/tests/WorkerTests.cs b/src/tests/WorkerTests.cs index 9f18876..58acd5b 100644 --- a/src/tests/WorkerTests.cs +++ b/src/tests/WorkerTests.cs @@ -341,7 +341,7 @@ public async Task ToBcfWithFileStream() { "Resources/Bcf/v2.1/AllPartsVisible.bcfzip", FileMode.Open, FileAccess.Read); - var bcf = await builder.BuildFromStream(stream); + var bcf = await builder.BuildInMemoryFromStream(stream); _worker.ToBcf(bcf, BcfVersionEnum.Bcf21, outputStream); await outputStream.FlushAsync(); diff --git a/src/tests/tests.csproj b/src/tests/tests.csproj index 2983214..882bbd6 100644 --- a/src/tests/tests.csproj +++ b/src/tests/tests.csproj @@ -12,6 +12,7 @@ +