From 6e81181a2516e645c4b56daf6f334d04b3105a73 Mon Sep 17 00:00:00 2001 From: Balint Bende Date: Thu, 5 Sep 2024 16:19:04 +0200 Subject: [PATCH] [BMSPT-283] fixed BCF21 version, added proces stream fns to bcf builder and converter, fixed and extended tests, extended readme --- README.md | 36 +++++ bcf-toolkit.sln.DotSettings.user | 45 +++--- src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs | 13 +- .../Builder/Bcf21/BcfBuilderExtensions.cs | 28 ++-- .../Builder/Bcf21/VisibilityBuilder.cs | 1 - src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs | 4 +- .../Builder/Bcf30/BcfBuilderExtensions.cs | 40 ++--- .../Builder/Interfaces/IBcfBuilderDelegate.cs | 8 +- src/bcf-toolkit/Converter/Bcf21/Converter.cs | 18 ++- src/bcf-toolkit/Converter/Bcf21/FileWriter.cs | 6 +- .../Converter/Bcf21/SchemaConverterToBcf30.cs | 2 +- src/bcf-toolkit/Converter/Bcf30/Converter.cs | 10 +- src/bcf-toolkit/Converter/Bcf30/FileWriter.cs | 4 +- .../Converter/Bcf30/SchemaConverterToBcf21.cs | 6 +- src/bcf-toolkit/Converter/IConverter.cs | 14 ++ .../Model/Bcf30/MarkupExtensions.cs | 2 +- src/bcf-toolkit/Program.cs | 69 +-------- src/bcf-toolkit/Utils/BcfExtensions.cs | 144 ++++++++++++++++-- ...derTests.cs => BcfBuilderInMemoryTests.cs} | 6 +- .../Builder/Bcf21/BcfBuilderStreamTest.cs | 48 ++++++ ...derTests.cs => BcfBuilderInMemoryTests.cs} | 47 +----- .../Builder/Bcf30/BcfBuilderStreamTest.cs | 48 ++++++ src/tests/Builder/BcfBuilderStreamTests.cs | 45 ------ src/tests/Converter/Bcf21/ConverterTests.cs | 6 +- src/tests/Converter/Bcf30/FileWriterTests.cs | 4 +- .../Json/v2.1/AllPartsVisible/imagebase64.txt | 1 + src/tests/Utils/BcfExtensionsTests.cs | 2 +- src/tests/Utils/JsonExtensionsTests.cs | 2 +- .../Utils/ZipArchiveEntryExtensionsTests.cs | 2 +- src/tests/WorkerTests.cs | 2 +- 30 files changed, 391 insertions(+), 272 deletions(-) rename src/tests/Builder/Bcf21/{BcfBuilderTests.cs => BcfBuilderInMemoryTests.cs} (97%) create mode 100644 src/tests/Builder/Bcf21/BcfBuilderStreamTest.cs rename src/tests/Builder/Bcf30/{BcfBuilderTests.cs => BcfBuilderInMemoryTests.cs} (58%) create mode 100644 src/tests/Builder/Bcf30/BcfBuilderStreamTest.cs delete mode 100644 src/tests/Builder/BcfBuilderStreamTests.cs create mode 100644 src/tests/Resources/Json/v2.1/AllPartsVisible/imagebase64.txt 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 13a66ad..faa1d46 100644 --- a/bcf-toolkit.sln.DotSettings.user +++ b/bcf-toolkit.sln.DotSettings.user @@ -1,30 +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" Name="BuildBcfFromV30StreamTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <TestAncestor> - <TestId>NUnit3x::8113526D-8A68-4E3E-B4DB-CE235875DDD1::net8.0::Tests.WorkerTests.BuildBcfFromV30StreamTest</TestId> - </TestAncestor> -</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> @@ -40,9 +22,6 @@ - <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> - <Solution /> -</SessionState> @@ -50,11 +29,23 @@ - <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 c7c4c62..3a22240 100644 --- a/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf21/BcfBuilder.cs @@ -12,7 +12,11 @@ public partial class BcfBuilder : IBcfBuilder< ProjectExtensionBuilder>, IDefaultBuilder { private readonly Bcf _bcf = new(); - + + public BcfBuilder() { + SetVersion(); + } + public BcfBuilder AddMarkup(Action builder) { var markup = (Markup)BuilderUtils.BuildItem(builder); @@ -28,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 d82c1d4..11748c7 100644 --- a/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs +++ b/src/bcf-toolkit/Builder/Bcf21/BcfBuilderExtensions.cs @@ -4,36 +4,32 @@ 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; -using Interfaces_IBcfBuilderDelegate = BcfToolkit.Builder.Interfaces.IBcfBuilderDelegate; namespace BcfToolkit.Builder.Bcf21; public partial class BcfBuilder { - private readonly Interfaces_IBcfBuilderDelegate? _delegate; - - public BcfBuilder(Interfaces_IBcfBuilderDelegate builderDelegate = null) { + private IBcfBuilderDelegate? _delegate; + + public void SetDelegate(IBcfBuilderDelegate? builderDelegate) { this._delegate = builderDelegate; - - _bcf.Version = new VersionBuilder() - .WithDefaults() - .Build(); } - public async Task ProcessStream(Stream source) { + + public Task ProcessStream(Stream source) { if (_delegate is null) { Console.WriteLine("IBcfBuilderDelegate is not set."); - return; + return Task.CompletedTask; } - // await BcfExtensions.ParseMarkups(source, _delegate.MarkupCreated); + var tasks = new List { + BcfExtensions.ParseMarkups(source, _delegate.MarkupCreated), + BcfExtensions.ParseProject(source, _delegate.ProjectCreated) + }; - // var extensions = await BcfExtensions.ParseExtensions(source); - // _delegate.ExtensionsCreated(extensions); - // - // _bcf.Project = await BcfExtensions.ParseProject(source); - // _bcf.Document = await BcfExtensions.ParseDocuments(source); + return Task.WhenAll(tasks); } public async Task BuildInMemoryFromStream(Stream 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 36a04d2..18df091 100644 --- a/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs +++ b/src/bcf-toolkit/Builder/Bcf30/BcfBuilder.cs @@ -14,9 +14,9 @@ public partial class BcfBuilder : IBcfBuilder< ExtensionsBuilder, DocumentInfoBuilder>, IDefaultBuilder { - + private readonly Bcf _bcf = new(); - + public BcfBuilder() { SetVersion(); } diff --git a/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs b/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs index b0b63cb..85bb31a 100644 --- a/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs +++ b/src/bcf-toolkit/Builder/Bcf30/BcfBuilderExtensions.cs @@ -10,32 +10,35 @@ namespace BcfToolkit.Builder.Bcf30; public partial class BcfBuilder { - + private IBcfBuilderDelegate? _delegate; - + public void SetDelegate(IBcfBuilderDelegate? builderDelegate) { this._delegate = builderDelegate; } - - public async Task ProcessStream(Stream source) { + + public Task ProcessStream(Stream source) { if (_delegate is null) { Console.WriteLine("IBcfBuilderDelegate is not set."); - return; + return Task.CompletedTask; } - await BcfExtensions.ParseMarkups( - source, - _delegate.MarkupCreated); - - // var extensions = await BcfExtensions.ParseExtensions(source); - // _delegate.ExtensionsCreated(extensions); - // - // _bcf.Project = await BcfExtensions.ParseProject(source); - // _bcf.Document = await BcfExtensions.ParseDocuments(source); - // var extensions = await BcfExtensions.ParseExtensions(source); + var tasks = new List { + BcfExtensions.ParseMarkups( + source, + _delegate.MarkupCreated), + BcfExtensions.ParseExtensions( + source, + _delegate.ExtensionsCreated), + BcfExtensions.ParseProject( + source, + _delegate.ProjectCreated), + BcfExtensions.ParseDocuments( + source, + _delegate.DocumentCreatedCreated) + }; - // _bcf.Project = await BcfExtensions.ParseProject(source); - // _bcf.Document = await BcfExtensions.ParseDocuments(source); + return Task.WhenAll(tasks); } // @@ -46,8 +49,7 @@ await BcfExtensions.ParseMarkups( /// The file stream. /// Returns the built object. public async Task BuildInMemoryFromStream(Stream source) { - _bcf.Markups = - await BcfExtensions.ParseMarkups(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/Interfaces/IBcfBuilderDelegate.cs b/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs index 5c6d188..a86ea1a 100644 --- a/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs +++ b/src/bcf-toolkit/Builder/Interfaces/IBcfBuilderDelegate.cs @@ -5,19 +5,19 @@ 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; } diff --git a/src/bcf-toolkit/Converter/Bcf21/Converter.cs b/src/bcf-toolkit/Converter/Bcf21/Converter.cs index 33e3a84..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; @@ -19,7 +20,7 @@ namespace BcfToolkit.Converter.Bcf21; /// public class Converter : IConverter { private BcfBuilder _builder = new(); - + /// /// Defines the converter function, which must be used for converting the /// BCF object to the targeted version. @@ -57,7 +58,7 @@ private readonly Dictionary< }; public async Task BcfToJson(Stream source, string targetPath) { - var bcf = await _builder.BuildInMemoryFromStream(source); + var bcf = await _builder.BuildInMemoryFromStream(source); await FileWriter.WriteBcfToJson(bcf, targetPath); } @@ -144,17 +145,18 @@ public Task ToBcf(IBcf bcf, string target) { public Task ToJson(IBcf bcf, string target) { return FileWriter.WriteBcfToJson((Bcf)bcf, target); } - + public async Task BcfFromStream(Stream stream) { var bcf = await _builder.BuildInMemoryFromStream(stream); var targetVersion = BcfVersion.TryParse(typeof(T)); var converterFn = _converterFn[targetVersion]; return (T)converterFn(bcf); } - - public async Task ProcessStream(Stream stream) { - // var targetVersion = BcfVersion.TryParse(typeof(T)); - _builder.Set - await _builder.ProcessStream(stream); + + 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/FileWriter.cs b/src/bcf-toolkit/Converter/Bcf21/FileWriter.cs index c78090a..07e55b6 100644 --- a/src/bcf-toolkit/Converter/Bcf21/FileWriter.cs +++ b/src/bcf-toolkit/Converter/Bcf21/FileWriter.cs @@ -107,7 +107,7 @@ public static void SerializeAndWriteBcfToStream( } var topicFolder = $"{guid}"; - + zip.CreateEntryFromObject($"{topicFolder}/markup.bcf", markup); foreach (var viewpoint in markup.Viewpoints) { @@ -194,13 +194,13 @@ public static async Task SerializeAndWriteBcfToFolder( topicFolder, "markup.bcf", markup)); - + foreach (var viewpoint in markup.Viewpoints) { writeTasks.Add(BcfExtensions.SerializeAndWriteXmlFile( topicFolder, viewpoint.Viewpoint, viewpoint.VisualizationInfo)); - + var snapshotFileName = viewpoint.Snapshot; var snapshotBase64String = viewpoint.SnapshotData?.Data; if (string.IsNullOrEmpty(snapshotFileName) || snapshotBase64String == null) diff --git a/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs b/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs index b72c384..ed804b1 100644 --- a/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs +++ b/src/bcf-toolkit/Converter/Bcf21/SchemaConverterToBcf30.cs @@ -54,7 +54,7 @@ public static Model.Bcf30.Bcf Convert(Model.Bcf21.Bcf from) { return builder.Build(); } - + // public static async Task ProcessStream(Stream stream) { // var del = new BcfBuilderDelegate(); // var fromBuilder = new BcfToolkit.Builder.Bcf21.BcfBuilder(del); diff --git a/src/bcf-toolkit/Converter/Bcf30/Converter.cs b/src/bcf-toolkit/Converter/Bcf30/Converter.cs index a67456c..f16914e 100644 --- a/src/bcf-toolkit/Converter/Bcf30/Converter.cs +++ b/src/bcf-toolkit/Converter/Bcf30/Converter.cs @@ -139,18 +139,18 @@ public Task ToBcf(IBcf bcf, string target) { public Task ToJson(IBcf bcf, string target) { return FileWriter.WriteJson((Bcf)bcf, target); } - + public async Task BcfFromStream(Stream stream) { var bcf = await _builder.BuildInMemoryFromStream(stream); var targetVersion = BcfVersion.TryParse(typeof(T)); var converterFn = _converterFn[targetVersion]; return (T)converterFn(bcf); } - - public async Task ProcessStream( - Stream stream, + + public Task ProcessStream( + Stream stream, IBcfBuilderDelegate builderDelegate) { _builder.SetDelegate(builderDelegate); - await _builder.ProcessStream(stream); + 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 2fd04f2..022fddd 100644 --- a/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs +++ b/src/bcf-toolkit/Converter/Bcf30/FileWriter.cs @@ -110,7 +110,7 @@ public static async Task SerializeAndWriteBcf(IBcf bcf, public static void SerializeAndWriteBcfToStream(IBcf bcf, ZipArchive zip, CancellationToken? cancellationToken = null) { var bcfObject = (Bcf)bcf; - + zip.CreateEntryFromObject("bcf.version", new Version()); // Writing markup files to zip arhive, one markup per entry. @@ -206,7 +206,7 @@ public static async Task SerializeAndWriteBcfToFolder( writeTasks.Add( BcfExtensions.SerializeAndWriteXmlFile(topicFolder, "markup.bcf", markup)); - + foreach (var viewpoint in markup.Topic.Viewpoints) { writeTasks.Add(BcfExtensions.SerializeAndWriteXmlFile( topicFolder, diff --git a/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs b/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs index 29aa44c..6c93cb2 100644 --- a/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs +++ b/src/bcf-toolkit/Converter/Bcf30/SchemaConverterToBcf21.cs @@ -206,15 +206,15 @@ private static Model.Bcf21.ViewPoint ConvertViewPoint( return builder .SetCameraDirection( - from.CameraDirection.X, + from.CameraDirection.X, from.CameraDirection.Y, from.CameraDirection.Z) .SetCameraViewPoint( - from.CameraViewPoint.X, + from.CameraViewPoint.X, from.CameraViewPoint.Y, from.CameraViewPoint.Z) .SetCameraUpVector( - from.CameraUpVector.X, + from.CameraUpVector.X, from.CameraUpVector.Y, from.CameraUpVector.Z) .SetFieldOfView(from.FieldOfView) 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/Bcf30/MarkupExtensions.cs b/src/bcf-toolkit/Model/Bcf30/MarkupExtensions.cs index 2ca9218..4c13b8f 100644 --- a/src/bcf-toolkit/Model/Bcf30/MarkupExtensions.cs +++ b/src/bcf-toolkit/Model/Bcf30/MarkupExtensions.cs @@ -14,7 +14,7 @@ public ITopic GetTopic() { public IViewPoint? GetFirstViewPoint() { return Topic.Viewpoints?.FirstOrDefault(); } - + public void SetViewPoints( Dictionary? visInfos, Dictionary? snapshots) where TVisualizationInfo : IVisualizationInfo { diff --git a/src/bcf-toolkit/Program.cs b/src/bcf-toolkit/Program.cs index c78506c..48b0e62 100644 --- a/src/bcf-toolkit/Program.cs +++ b/src/bcf-toolkit/Program.cs @@ -1,55 +1,14 @@ using System; using System.Threading.Tasks; using System.CommandLine; -using System.IO; -using BcfToolkit.Builder.Bcf21; -using BcfToolkit.Builder.Interfaces; -using BcfToolkit.Model.Bcf21; -using BcfToolkit.Model.Interfaces; using Serilog; using Serilog.Events; namespace BcfToolkit; -public class BcfBuilderDelegate : IBcfBuilderDelegate { - public IBcfBuilderDelegate.OnMarkupCreated - MarkupCreated { get; } = m => { - var d = ((Markup)m).Topic.Description; - Console.WriteLine(d); - }; - - public IBcfBuilderDelegate.OnExtensionsCreated ExtensionsCreated { - get; - } - - - public IBcfBuilderDelegate.OnProjectCreated - ProjectCreated { get; } = Console.WriteLine; - - public IBcfBuilderDelegate.OnDocumentCreated DocumentCreatedCreated { - get; - } -} - internal static class Program { private static async Task Main(string[] args) { - await using var stream = new FileStream( - "/Users/balintbende/Developer/test/bcf/bcftoolkittest.bcfzip", - FileMode.Open, - FileAccess.Read); - - var bcfBuilderDelegate = new BcfBuilderDelegate(); - var streamBuilder = new BcfBuilder(bcfBuilderDelegate); - await streamBuilder.ProcessStream(stream); - - // var builder = new BcfBuilder(); - // var bcf = await builder.BuildInMemoryFromStream(stream); - // Console.WriteLine(bcf.Markups.Count); - - stream.Close(); - - // await HandleArguments(args); - Environment.Exit(0); + await HandleArguments(args); } private static async Task HandleArguments(string[] args) { // Logger setup @@ -62,7 +21,7 @@ private static async Task HandleArguments(string[] args) { .CreateLogger(); 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 }; @@ -93,33 +52,13 @@ private static async Task HandleArguments(string[] args) { private static async Task DoRootCommand(InputArguments arguments) { try { - // var worker = new Worker(); - // await worker.Convert(arguments.SourcePath, arguments.Target); - await using var stream = new FileStream( - "/Users/balintbende/Developer/test/bcf/NBHU_BT_BEHF.bcfzip", - FileMode.Open, - FileAccess.Read); - - // var bcfBuilderDelegate = new BcfBuilderDelegate(); - // var streamBuilder = new BcfBuilder(bcfBuilderDelegate); - // await streamBuilder.ProcessStream(stream); - - - - var builder = new BcfBuilder(); - var bcf = await builder.BuildInMemoryFromStream(stream); - Console.WriteLine(bcf.Markups.Count); - - builder = null; - bcf = null; - - stream.Close(); + var worker = new Worker(); + await worker.Convert(arguments.SourcePath, arguments.Target); } catch (Exception e) { 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 b22f09e..5aa2718 100644 --- a/src/bcf-toolkit/Utils/BcfExtensions.cs +++ b/src/bcf-toolkit/Utils/BcfExtensions.cs @@ -55,6 +55,23 @@ public static async Task> ParseMarkups< 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>( @@ -108,7 +125,7 @@ private static async Task> _ParseMarkups< !string.IsNullOrEmpty(currentUuid) && uuid != currentUuid; if (isNewTopic) - WritingOutMarkup( + AddOrInvokeMarkup( ref markup, ref visInfos, ref snapshots, @@ -145,7 +162,7 @@ private static async Task> _ParseMarkups< } if (isLastTopicEntry) - WritingOutMarkup( + AddOrInvokeMarkup( ref markup, ref visInfos, ref snapshots, @@ -159,7 +176,19 @@ private static async Task> _ParseMarkups< return markups; } - private static void WritingOutMarkup( + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static void AddOrInvokeMarkup( ref TMarkup? markup, ref Dictionary? visInfos, ref Dictionary? snapshots, @@ -173,8 +202,8 @@ private static void WritingOutMarkup( markup.SetViewPoints(visInfos, snapshots); // If a delegate is provided, invoke it without adding markup to the BCF - if(onMarkupCreated is not null) - onMarkupCreated.Invoke(markup); + if (onMarkupCreated is not null) + onMarkupCreated?.Invoke(markup); else markups.Add(markup); @@ -199,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; } /// @@ -214,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); } /// @@ -229,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."); @@ -267,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; @@ -357,11 +469,11 @@ public static Task SerializeAndWriteXmlFile(string folder, string file, /// Returns the BcfVersionEnum enum. public static async Task GetVersionFromStreamArchive( Stream stream) { - + if (!stream.CanRead || !stream.CanSeek) { throw new ArgumentException("Stream is not Readable or Seekable"); } - + using var archive = new ZipArchive(stream, ZipArchiveMode.Read, true); BcfVersionEnum? version = null; 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/Bcf21/BcfBuilderStreamTest.cs b/src/tests/Builder/Bcf21/BcfBuilderStreamTest.cs new file mode 100644 index 0000000..b931684 --- /dev/null +++ b/src/tests/Builder/Bcf21/BcfBuilderStreamTest.cs @@ -0,0 +1,48 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using BcfToolkit.Builder.Bcf21; +using BcfToolkit.Builder.Interfaces; +using BcfToolkit.Model.Bcf21; +using BcfToolkit.Model.Interfaces; +using NUnit.Framework; + +namespace Tests.Builder.Bcf21; + +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/v2.1/MaximumInformation.bcfzip", + FileMode.Open, + FileAccess.Read); + + await _streamBuilder.ProcessStream(stream); + } +} \ No newline at end of file diff --git a/src/tests/Builder/Bcf30/BcfBuilderTests.cs b/src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs similarity index 58% rename from src/tests/Builder/Bcf30/BcfBuilderTests.cs rename to src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs index e071847..d7d113c 100644 --- a/src/tests/Builder/Bcf30/BcfBuilderTests.cs +++ b/src/tests/Builder/Bcf30/BcfBuilderInMemoryTests.cs @@ -1,39 +1,19 @@ -using System; using System.IO; using System.Linq; using System.Threading.Tasks; using BcfToolkit.Builder.Bcf30; -using BcfToolkit.Builder.Bcf30.Interfaces; -using BcfToolkit.Model.Bcf30; using NUnit.Framework; -namespace tests.Builder.Bcf30; +namespace Tests.Builder.Bcf30; -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; -} - -public class BcfBuilderTests { +public class BcfBuilderInMemoryTests { private BcfBuilder _inMemoryBuilder = null!; - private BcfBuilder _steamBuilder = null!; [SetUp] public void Setup() { _inMemoryBuilder = new BcfBuilder(); - var bcfBuilderDelegate = new BcfBuilderDelegate(); - _steamBuilder = new BcfBuilder(bcfBuilderDelegate); } - + [Test] public void BuildBcfWithComplexFields() { var bcf = _inMemoryBuilder @@ -55,26 +35,11 @@ public void BuildBcfWithComplexFields() { Assert.That(true, Is.EqualTo(bcf.Extensions.TopicTypesSpecified)); Assert.That("Project", Is.EqualTo(bcf.Project?.Project.Name)); } - + [Test] public void BuildBcfWithMissingRequiredFields() { Assert.That(() => _inMemoryBuilder.Build(), Throws.ArgumentException); } - - - [Test] - public async Task BuildInMemoryBcfFromStreamTest() { - await using var stream = new FileStream( - "Resources/Bcf/v3.0/UserAssignment.bcfzip", - FileMode.Open, - FileAccess.Read); - - await _steamBuilder.ProcessStream(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 BuildInMemoryBcfFromStream() { @@ -88,7 +53,7 @@ public async Task BuildInMemoryBcfFromStream() { "Architect@example.com", Is.EqualTo(bcf.Markups.FirstOrDefault()?.Topic.AssignedTo)); } - + [Test] public async Task BuildEmptyBcfFromStreamTest() { await using var stream = new FileStream( @@ -96,6 +61,6 @@ public async Task BuildEmptyBcfFromStreamTest() { FileMode.Open, FileAccess.Read); Assert.That(() => _inMemoryBuilder.BuildInMemoryFromStream(stream), - Throws.ArgumentException); + 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/BcfBuilderStreamTests.cs b/src/tests/Builder/BcfBuilderStreamTests.cs deleted file mode 100644 index 49ed74e..0000000 --- a/src/tests/Builder/BcfBuilderStreamTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using BcfToolkit; -using NUnit.Framework; -using BcfToolkit.Builder.Bcf30; -using Moq; - -namespace Tests.Builder; - -using BcfToolkit.Builder.Bcf30.Interfaces; -using BcfToolkit.Model.Bcf30; - -public class BcfBuilderStreamTests { - private BcfBuilder _streamBuilder = null!; - - // private class BcfBuilderDelegate : IBcfBuilderDelegate { - // public IBcfBuilderDelegate.OnMarkupCreated - // OnMarkupCreated { get; } = Console.WriteLine; - // - // public IBcfBuilderDelegate.OnExtensionsCreated - // ExtensionsCreated { get; } = Console.WriteLine; - // - // public IBcfBuilderDelegate.OnProjectCreated - // ProjectCreated { get; } = Console.WriteLine; - // - // public IBcfBuilderDelegate.OnDocumentCreated - // DocumentCreatedCreated { get; } = Console.WriteLine; - // } - - [Test] - public async Task ProcessStreamTest() { - var bcfBuilderDelegateMock = new Mock(); - _streamBuilder = new BcfBuilder(bcfBuilderDelegateMock.Object); - - await using var stream = new FileStream( - "Resources/Bcf/v3.0/UserAssignment.bcfzip", - FileMode.Open, - FileAccess.Read); - await _streamBuilder.ProcessStream(stream); - - bcfBuilderDelegateMock - .Verify(d => d.MarkupCreated, Times.Once); - } -} \ No newline at end of file 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/Bcf30/FileWriterTests.cs b/src/tests/Converter/Bcf30/FileWriterTests.cs index 3d22132..0df62ec 100644 --- a/src/tests/Converter/Bcf30/FileWriterTests.cs +++ b/src/tests/Converter/Bcf30/FileWriterTests.cs @@ -29,7 +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, @@ -50,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/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 938c225..58acd5b 100644 --- a/src/tests/WorkerTests.cs +++ b/src/tests/WorkerTests.cs @@ -142,7 +142,7 @@ private async Task CheckBcfStreamVersion( Assert.That(expectedVersion, Is.EqualTo(version)); await stream.FlushAsync(); } - + // [Test] // [Category("BCF v3.0")] // public async Task BcfV30ToV21StreamSamplesTests() {