mirror of
				https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
				synced 2025-10-30 17:55:32 +00:00 
			
		
		
		
	Added Classes for detecting Root Elements of Hamann-Collections
This commit is contained in:
		| @@ -6,6 +6,7 @@ using Microsoft.FeatureManagement.Mvc; | |||||||
| using System.IO; | using System.IO; | ||||||
| using System.Net; | using System.Net; | ||||||
| using System.Text; | using System.Text; | ||||||
|  | using System.Runtime.InteropServices; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using Microsoft.AspNetCore.Http; | using Microsoft.AspNetCore.Http; | ||||||
| using Microsoft.AspNetCore.Http.Features; | using Microsoft.AspNetCore.Http.Features; | ||||||
| @@ -14,6 +15,9 @@ using Microsoft.Extensions.Configuration; | |||||||
| using Microsoft.Net.Http.Headers; | using Microsoft.Net.Http.Headers; | ||||||
| using HaWeb.Filters; | using HaWeb.Filters; | ||||||
| using HaWeb.FileHelpers; | using HaWeb.FileHelpers; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  | using HaWeb.Models; | ||||||
|  | using System.Xml.Linq; | ||||||
|  |  | ||||||
| public class UploadController : Controller | public class UploadController : Controller | ||||||
| { | { | ||||||
| @@ -22,18 +26,24 @@ public class UploadController : Controller | |||||||
|     private IReaderService _readerService; |     private IReaderService _readerService; | ||||||
|     private readonly long _fileSizeLimit; |     private readonly long _fileSizeLimit; | ||||||
|     private readonly string _targetFilePath; |     private readonly string _targetFilePath; | ||||||
|  |     private readonly IXMLService _xmlService; | ||||||
|  |  | ||||||
|     // Options |     // Options | ||||||
|     private static readonly string[] _permittedExtensions = { ".xml" }; |     private static readonly string[] _permittedExtensions = { ".xml" }; | ||||||
|     private static readonly FormOptions _defaultFormOptions = new FormOptions(); |     private static readonly FormOptions _defaultFormOptions = new FormOptions(); | ||||||
|  |  | ||||||
|  |  | ||||||
|     public UploadController(ILibrary lib, IReaderService readerService, IConfiguration config) |     public UploadController(ILibrary lib, IReaderService readerService, IXMLService xmlService, IConfiguration config) | ||||||
|     { |     { | ||||||
|         _lib = lib; |         _lib = lib; | ||||||
|         _readerService = readerService; |         _readerService = readerService; | ||||||
|  |         _xmlService = xmlService; | ||||||
|         _fileSizeLimit = config.GetValue<long>("FileSizeLimit"); |         _fileSizeLimit = config.GetValue<long>("FileSizeLimit"); | ||||||
|         _targetFilePath = config.GetValue<string>("StoredFilesPath"); |         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { | ||||||
|  |             _targetFilePath = config.GetValue<string>("StoredFilePathWindows"); | ||||||
|  |         } else { | ||||||
|  |             _targetFilePath = config.GetValue<string>("StoredFilePathLinux"); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     [HttpGet] |     [HttpGet] | ||||||
| @@ -42,72 +52,86 @@ public class UploadController : Controller | |||||||
|     [GenerateAntiforgeryTokenCookie] |     [GenerateAntiforgeryTokenCookie] | ||||||
|     public IActionResult Index() |     public IActionResult Index() | ||||||
|     { |     { | ||||||
|         return View("../Admin/Upload/Index"); |         var model = new UploadViewModel(); | ||||||
|  |         model.AvailableRoots = _xmlService.GetRoots().Select(x => (x.Type, "")).ToList(); | ||||||
|  |         return View("../Admin/Upload/Index", model); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | //// UPLOAD //// | ||||||
|     [HttpPost] |     [HttpPost] | ||||||
|     [Route("Admin/Upload")] |     [Route("Admin/Upload")] | ||||||
|     [DisableFormValueModelBinding] |     [DisableFormValueModelBinding] | ||||||
|     [ValidateAntiForgeryToken] |     [ValidateAntiForgeryToken] | ||||||
|     public async Task<IActionResult> Post() { |     public async Task<IActionResult> Post() { | ||||||
|  | //// 1. Stage: Check Request format and request spec | ||||||
| //// 1. Stage: Check Request and File on a byte-level |         // Checks the Content-Type Field (must be multipart + Boundary) | ||||||
|         // Checks the COntent-Type Field (must be multipart + Boundary) |  | ||||||
|         if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) |         if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) | ||||||
|         { |         { | ||||||
|             ModelState.AddModelError("File", $"Wrong / No Content Type on the Request"); |             ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request"); | ||||||
|             return BadRequest(ModelState); |             return BadRequest(ModelState); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Divides the multipart document into it's sections and sets up a reader |         // Divides the multipart document into it's sections and sets up a reader | ||||||
|         var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); |         var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit); | ||||||
|         var reader = new MultipartReader(boundary, HttpContext.Request.Body); |         var reader = new MultipartReader(boundary, HttpContext.Request.Body); | ||||||
|         var section = await reader.ReadNextSectionAsync(); |         MultipartSection? section = null; | ||||||
|  |         try { | ||||||
|  |             section = await reader.ReadNextSectionAsync(); | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) { | ||||||
|  |             ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         while (section != null) |         while (section != null) | ||||||
|         { |         { | ||||||
|             // Multipart document content disposition header read for a section: |             // Multipart document content disposition header read for a section: | ||||||
|             // Starts with boundary, contains field name, content-dispo, filename, content-type |             // Starts with boundary, contains field name, content-dispo, filename, content-type | ||||||
|             var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); |             var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition); | ||||||
|  |  | ||||||
|             if (hasContentDispositionHeader && contentDisposition != null) |             if (hasContentDispositionHeader && contentDisposition != null) | ||||||
|             { |             { | ||||||
|                 // Checks if it is a section with content-disposition, name, filename |                 // Checks if it is a section with content-disposition, name, filename | ||||||
|                 if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) |                 if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) | ||||||
|                 { |                 { | ||||||
|                     ModelState.AddModelError("File", $"Wrong Content-Dispostion Headers in Multipart Document"); |                     ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document"); | ||||||
|                     return BadRequest(ModelState); |                     return BadRequest(ModelState); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Sanity checks on the file on a byte level, extension checking, is it empty etc. | //// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc. | ||||||
|                 var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( |                 var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile( | ||||||
|                     section, contentDisposition, ModelState,  |                     section, contentDisposition, ModelState,  | ||||||
|                     _permittedExtensions, _fileSizeLimit); |                     _permittedExtensions, _fileSizeLimit); | ||||||
|  |                 if (!ModelState.IsValid || streamedFileContent == null) | ||||||
|                 if (!ModelState.IsValid) |  | ||||||
|                     return BadRequest(ModelState); |                     return BadRequest(ModelState); | ||||||
|  |  | ||||||
| //// 2. Stage: Valid XML checking | //// 3. Stage: Valid XML checking using a simple XDocument.Load() | ||||||
|  |                 var xdocument = await XDocumentFileHelper.ProcessStreamedFile(streamedFileContent, ModelState); | ||||||
|  |                 if (!ModelState.IsValid || xdocument == null) | ||||||
|  |                     return UnprocessableEntity(ModelState); | ||||||
|  |  | ||||||
| //// 3. Stage: Is it a Hamann-Document? What kind? | //// 3. Stage: Is it a Hamann-Document? What kind? | ||||||
|  |                 var docs = _xmlService.ProbeHamannFile(xdocument, ModelState); | ||||||
|  |                 if (!ModelState.IsValid || docs == null || !docs.Any()) | ||||||
|  |                     return UnprocessableEntity(ModelState); | ||||||
|                  |                  | ||||||
| //// 4. Stage: Get Filename for the stageing area | //// 5. Stage: Saving the File(s) | ||||||
|  |                 foreach (var doc in docs) { | ||||||
| //// 5. Stage: Saving the File |                     using (var targetStream = System.IO.File.Create(Path.Combine(_targetFilePath, doc.CreateFilename()))) | ||||||
|                 // // Encode Filename for display |                         doc.Save(targetStream); | ||||||
|                 // var trustedFileNameForDisplay = WebUtility.HtmlEncode(contentDisposition.FileName.Value); |  | ||||||
|  |  | ||||||
|  |  | ||||||
|                 // // TODO: generatre storage filename |  | ||||||
|                 // var trustedFileNameForFileStorage = Path.GetRandomFileName(); |  | ||||||
|                 // using (var targetStream = System.IO.File.Create(Path.Combine(_targetFilePath, trustedFileNameForFileStorage))) |  | ||||||
|                 //     await targetStream.WriteAsync(streamedFileContent); |  | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|             // Drain any remaining section body that hasn't been consumed and |                 return Created(nameof(UploadController), docs); | ||||||
|             // read the headers for the next section. |             } | ||||||
|  |  | ||||||
|  |            try | ||||||
|  |             { | ||||||
|                 section = await reader.ReadNextSectionAsync(); |                 section = await reader.ReadNextSectionAsync(); | ||||||
|             } |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 ModelState.AddModelError("Error", "The Request is bad: " + ex.Message); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
| //// Success! Return Last Created File View | //// Success! Return Last Created File View | ||||||
|         return Created(nameof(UploadController), null); |         return Created(nameof(UploadController), null); | ||||||
|   | |||||||
							
								
								
									
										38
									
								
								HaWeb/FileHelpers/XDocumentFileHelpers.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								HaWeb/FileHelpers/XDocumentFileHelpers.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | |||||||
|  | namespace HaWeb.FileHelpers; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
|  | using System.Text; | ||||||
|  | using System.Xml; | ||||||
|  |  | ||||||
|  | public static class XDocumentFileHelper { | ||||||
|  |  | ||||||
|  |     private readonly static XmlReaderSettings _Settings = new XmlReaderSettings() | ||||||
|  |     { | ||||||
|  |         CloseInput = true, | ||||||
|  |         CheckCharacters = false, | ||||||
|  |         ConformanceLevel = ConformanceLevel.Fragment, | ||||||
|  |         IgnoreComments = true, | ||||||
|  |         IgnoreProcessingInstructions = true, | ||||||
|  |         IgnoreWhitespace = false | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public static async Task<XDocument?> ProcessStreamedFile(byte[] bytes, ModelStateDictionary modelState) { | ||||||
|  |         try | ||||||
|  |         { | ||||||
|  |             using (var stream = new MemoryStream(bytes)) | ||||||
|  |             { | ||||||
|  |                 using (var xmlreader = XmlReader.Create(stream, _Settings)) | ||||||
|  |                 { | ||||||
|  |                     return XDocument.Load(xmlreader, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo); | ||||||
|  |  | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         catch (Exception ex) | ||||||
|  |         { | ||||||
|  |             modelState.AddModelError("Error", $"Kein gültiges XML-Dokument geladen. Error: {ex.Message}"); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -140,7 +140,7 @@ public static class XMLFileHelpers | |||||||
|     //     return Array.Empty<byte>(); |     //     return Array.Empty<byte>(); | ||||||
|     // } |     // } | ||||||
|  |  | ||||||
|     public static async Task<byte[]> ProcessStreamedFile( |     public static async Task<byte[]?> ProcessStreamedFile( | ||||||
|         MultipartSection section, ContentDispositionHeaderValue contentDisposition,  |         MultipartSection section, ContentDispositionHeaderValue contentDisposition,  | ||||||
|         ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) |         ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) | ||||||
|     { |     { | ||||||
| @@ -152,16 +152,16 @@ public static class XMLFileHelpers | |||||||
|  |  | ||||||
|                 // Check if the file is empty or exceeds the size limit. |                 // Check if the file is empty or exceeds the size limit. | ||||||
|                 if (memoryStream.Length == 0) |                 if (memoryStream.Length == 0) | ||||||
|                     modelState.AddModelError("File", "The file is empty."); |                     modelState.AddModelError("Error", "The file is empty."); | ||||||
|                 else if (memoryStream.Length > sizeLimit) |                 else if (memoryStream.Length > sizeLimit) | ||||||
|                 { |                 { | ||||||
|                     var megabyteSizeLimit = sizeLimit / 1048576; |                     var megabyteSizeLimit = sizeLimit / 1048576; | ||||||
|                     modelState.AddModelError("File", $"The file exceeds {megabyteSizeLimit:N1} MB."); |                     modelState.AddModelError("Error", $"The file exceeds {megabyteSizeLimit:N1} MB."); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 // Check file extension and first bytes |                 // Check file extension and first bytes | ||||||
|                 else if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, memoryStream, permittedExtensions)) |                 else if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, memoryStream, permittedExtensions)) | ||||||
|                     modelState.AddModelError("File", "The file must be of the following specs:<br>" + |                     modelState.AddModelError("Error", "The file must be of the following specs:<br>" + | ||||||
|                         "1. The file must hava a .xml File-Extension<br>" + |                         "1. The file must hava a .xml File-Extension<br>" + | ||||||
|                         "2. To make sure the file isn't executable the file must start with: <?xml version=\"1.0\" encoding=\"utf-8\"?> or <?xml version=\"1.0\"?>"); |                         "2. To make sure the file isn't executable the file must start with: <?xml version=\"1.0\" encoding=\"utf-8\"?> or <?xml version=\"1.0\"?>"); | ||||||
|                  |                  | ||||||
| @@ -171,10 +171,10 @@ public static class XMLFileHelpers | |||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|             modelState.AddModelError("File", $"The upload failed. Error: {ex.HResult}"); |             modelState.AddModelError("Error", $"The upload failed. Error: {ex.Message}"); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         return Array.Empty<byte>(); |         return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) |     private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
|  |  | ||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <TargetFramework>net6.0</TargetFramework> |     <TargetFramework>net6.0</TargetFramework> | ||||||
|  |     <EnablePreviewFeatures>True</EnablePreviewFeatures> | ||||||
|     <Nullable>enable</Nullable> |     <Nullable>enable</Nullable> | ||||||
|     <ImplicitUsings>enable</ImplicitUsings> |     <ImplicitUsings>enable</ImplicitUsings> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
| @@ -24,7 +25,6 @@ | |||||||
|     <Exec Command="npm run css:build" /> |     <Exec Command="npm run css:build" /> | ||||||
|   </Target> |   </Target> | ||||||
|   <ItemGroup> |   <ItemGroup> | ||||||
|     <PackageReference Include="LigerShark.WebOptimizer.Core" Version="3.0.357" /> |  | ||||||
|     <PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" /> |     <PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.5.1" /> | ||||||
|   </ItemGroup> |   </ItemGroup> | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										5
									
								
								HaWeb/Models/UploadViewModel.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								HaWeb/Models/UploadViewModel.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | namespace HaWeb.Models; | ||||||
|  |  | ||||||
|  | public class UploadViewModel { | ||||||
|  |     public List<(string, string)>? AvailableRoots { get; set; } | ||||||
|  | } | ||||||
| @@ -1,18 +1,29 @@ | |||||||
| using HaXMLReader; | using HaXMLReader; | ||||||
| using HaXMLReader.Interfaces; | using HaXMLReader.Interfaces; | ||||||
| using HaDocument.Interfaces; | using HaDocument.Interfaces; | ||||||
|  | using HaWeb.XMLParser; | ||||||
| using Microsoft.FeatureManagement; | using Microsoft.FeatureManagement; | ||||||
|  |  | ||||||
|  | using Microsoft.Extensions.Configuration; | ||||||
|  | using Microsoft.Extensions.DependencyInjection; | ||||||
|  | using Microsoft.Extensions.FileProviders; | ||||||
|  |  | ||||||
| var builder = WebApplication.CreateBuilder(args); | var builder = WebApplication.CreateBuilder(args); | ||||||
|  |  | ||||||
| // Add services to the container. | // Add services to the container. | ||||||
| builder.Services.AddControllersWithViews(); | builder.Services.AddControllersWithViews(); | ||||||
| builder.Services.AddHttpContextAccessor(); | builder.Services.AddHttpContextAccessor(); | ||||||
| builder.Services.AddSingleton<ILibrary>(x => HaDocument.Document.Create(new Options())); |  | ||||||
| builder.Services.AddTransient<IReaderService, ReaderService>(); |  | ||||||
| builder.Services.AddFeatureManagement(); |  | ||||||
|  |  | ||||||
| // builder.Services.AddWebOptimizer(); | // // To list physical files from a path provided by configuration: | ||||||
|  | // var physicalProvider = new PhysicalFileProvider(Configuration.GetValue<string>("StoredFilesPath")); | ||||||
|  | // // To list physical files in the temporary files folder, use: | ||||||
|  | // //var physicalProvider = new PhysicalFileProvider(Path.GetTempPath()); | ||||||
|  | // services.AddSingleton<IFileProvider>(physicalProvider); | ||||||
|  |  | ||||||
|  | builder.Services.AddSingleton<ILibrary>(HaDocument.Document.Create(new Options())); | ||||||
|  | builder.Services.AddTransient<IReaderService, ReaderService>(); | ||||||
|  | builder.Services.AddSingleton<IXMLService, XMLService>(); | ||||||
|  | builder.Services.AddFeatureManagement(); | ||||||
|  |  | ||||||
| var app = builder.Build(); | var app = builder.Build(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| namespace HaWeb.Settings; | namespace HaWeb.Settings; | ||||||
|  | using HaWeb.Settings.XMLRoots; | ||||||
|  |  | ||||||
| public static class General { | public static class General { | ||||||
|     // Classes generated by parsing the XML: |     // Classes generated by parsing the XML: | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								HaWeb/Settings/XMLRoots/CommentRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								HaWeb/Settings/XMLRoots/CommentRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class CommentRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Register"; | ||||||
|  |     public string Container { get; } = "kommcat"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "kommentar") return true; | ||||||
|  |         else return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("id"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         var kat = element.Attribute("value"); | ||||||
|  |         if (kat != null && !String.IsNullOrWhiteSpace(kat.Value))  | ||||||
|  |             return (null, kat.Value); | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								HaWeb/Settings/XMLRoots/DescriptionsRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								HaWeb/Settings/XMLRoots/DescriptionsRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Metadaten"; | ||||||
|  |     public string Container { get; } = "descriptions"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "letterDesc") return true; | ||||||
|  |         return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("ref"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								HaWeb/Settings/XMLRoots/DocumentRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								HaWeb/Settings/XMLRoots/DocumentRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class DocumentRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Brieftext"; | ||||||
|  |     public string Container { get; } = "document"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "letterText") return true; | ||||||
|  |         else return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("index"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								HaWeb/Settings/XMLRoots/EditsRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								HaWeb/Settings/XMLRoots/EditsRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class EditsRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Texteingriffe"; | ||||||
|  |     public string Container { get; } = "edits"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "editreason") return true; | ||||||
|  |         else return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("index"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								HaWeb/Settings/XMLRoots/MarginalsRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								HaWeb/Settings/XMLRoots/MarginalsRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Stellenkommentar"; | ||||||
|  |     public string Container { get; } = "marginalien"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "marginal") return true; | ||||||
|  |         else return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("index"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								HaWeb/Settings/XMLRoots/ReferencesRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								HaWeb/Settings/XMLRoots/ReferencesRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Personen / Orte"; | ||||||
|  |     public string Container { get; } = "definitions"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "personDefs" || elem.Name == "structureDefs" || elem.Name == "handDefs" || elem.Name == "locationDefs") | ||||||
|  |             return true; | ||||||
|  |         return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         return elem.Name.ToString(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								HaWeb/Settings/XMLRoots/TraditionsRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								HaWeb/Settings/XMLRoots/TraditionsRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | namespace HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using HaWeb.XMLParser; | ||||||
|  |  | ||||||
|  | public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot { | ||||||
|  |     public string Type { get; } = "Überlieferung"; | ||||||
|  |     public string Container { get; } = "traditions"; | ||||||
|  |  | ||||||
|  |     public Predicate<XElement> IsCollectedObject { get; } = (elem) => { | ||||||
|  |         if (elem.Name == "letterTradition") return true; | ||||||
|  |         else return false; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public Func<XElement, string?> GetKey { get; } = (elem) => {  | ||||||
|  |         var index = elem.Attribute("ref"); | ||||||
|  |         if (index != null && !String.IsNullOrWhiteSpace(index.Value)) | ||||||
|  |             return index.Value; | ||||||
|  |         else return null; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     public List<(string, string)>? GenerateFields(XMLRootDocument document) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public (string?, string) GenerateIdentificationString(XElement element) { | ||||||
|  |         return (null, Container); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | @model UploadViewModel; | ||||||
| Hello from Upload Index! | Hello from Upload Index! | ||||||
|  |  | ||||||
| <form id="uploadForm" action="Upload" method="post"  | <form id="uploadForm" action="Upload" method="post"  | ||||||
| @@ -18,6 +19,11 @@ Hello from Upload Index! | |||||||
|     </div> |     </div> | ||||||
| </form> | </form> | ||||||
|  |  | ||||||
|  | @foreach (var item in Model.AvailableRoots.OrderBy(x => x.Item1)) | ||||||
|  | { | ||||||
|  |   <div>@item.Item1</div> | ||||||
|  | } | ||||||
|  |  | ||||||
| @section Scripts { | @section Scripts { | ||||||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" |     <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js" | ||||||
|             asp-fallback-src="~/lib/jquery/dist/jquery.min.js" |             asp-fallback-src="~/lib/jquery/dist/jquery.min.js" | ||||||
|   | |||||||
							
								
								
									
										47
									
								
								HaWeb/XMLParser/IXMLRoot.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								HaWeb/XMLParser/IXMLRoot.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | namespace HaWeb.XMLParser; | ||||||
|  | using System.Xml.Linq; | ||||||
|  |  | ||||||
|  | public interface IXMLRoot { | ||||||
|  |     // Name of the IXMLRoot | ||||||
|  |     public abstract string Type { get; } | ||||||
|  |  | ||||||
|  |     // Tag Name of the Container | ||||||
|  |     public abstract string Container { get; } | ||||||
|  |  | ||||||
|  |     // Tag Name of child objects to be collected  | ||||||
|  |     public abstract Predicate<XElement> IsCollectedObject { get; } | ||||||
|  |  | ||||||
|  |     // Gets the Key of a collected object | ||||||
|  |     public abstract Func<XElement, string?> GetKey { get; } | ||||||
|  |  | ||||||
|  |     // Is the pesented XElement such a root?  | ||||||
|  |     public bool IsTypeOf(XElement xelement) { | ||||||
|  |         if (xelement.Name == this.Container) return true; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Generate certain metadat fields to display about this root | ||||||
|  |     public abstract List<(string, string)>? GenerateFields(XMLRootDocument document); | ||||||
|  |  | ||||||
|  |     // Generate an identification string of which the hash will be the filename.  | ||||||
|  |     // The second string will be appended literally for convenience. | ||||||
|  |     // If the queries of two document are equal they replace each other | ||||||
|  |     // If the queries and the date of two documents are equal the later one gets deleted | ||||||
|  |     public abstract (string?, string) GenerateIdentificationString(XElement element); | ||||||
|  |  | ||||||
|  |     // Further deciding which of two documents replaces which | ||||||
|  |     public abstract bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2); | ||||||
|  |  | ||||||
|  |     public Dictionary<string, XElement>? GetCollectedObjects(XMLRootDocument document) { | ||||||
|  |         Dictionary<string, XElement>? ret = null; | ||||||
|  |         var root = document.Root; | ||||||
|  |         root.Elements().Where(x => this.IsCollectedObject(x)).ToList().ForEach(x => { | ||||||
|  |             var id = this.GetKey(x); | ||||||
|  |             if (id != null) { | ||||||
|  |                 if (ret == null) ret = new Dictionary<string, XElement>(); | ||||||
|  |                 ret.Add(id, x); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								HaWeb/XMLParser/IXMLService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								HaWeb/XMLParser/IXMLService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | namespace HaWeb.XMLParser; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
|  |  | ||||||
|  | public interface IXMLService { | ||||||
|  |     public List<IXMLRoot>? GetRoots(); | ||||||
|  |     public List<XMLRootDocument>? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState); | ||||||
|  | } | ||||||
							
								
								
									
										41
									
								
								HaWeb/XMLParser/XMLRootDocument.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								HaWeb/XMLParser/XMLRootDocument.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | namespace HaWeb.XMLParser; | ||||||
|  | using System.Xml.Linq; | ||||||
|  |  | ||||||
|  | public class XMLRootDocument { | ||||||
|  |     public string Type {get; private set; } | ||||||
|  |     public DateTime Date { get; private set; } | ||||||
|  |     public XElement Root { get; private set; } | ||||||
|  |     public (string?, string) IdentificationString { get; private set; } | ||||||
|  |  | ||||||
|  |     public Dictionary<string, XElement>? Elements { get; set; } | ||||||
|  |     public List<(string, string)>? Fields { get; set; } | ||||||
|  |  | ||||||
|  |     public XMLRootDocument(string type, (string?, string) idString, XElement root) { | ||||||
|  |         Type = type; | ||||||
|  |         IdentificationString = idString; | ||||||
|  |         Date = DateTime.Today; | ||||||
|  |         Root = root; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public string CreateFilename() { | ||||||
|  |         var filename = _removeInvalidChars(Type) + "_"; | ||||||
|  |         if (IdentificationString.Item1 != null) filename += _removeInvalidChars(IdentificationString.Item1) + "_";  | ||||||
|  |         filename += _removeInvalidChars(IdentificationString.Item2) + "_"; | ||||||
|  |         filename += _removeInvalidChars(Date.Year.ToString() + "-" + Date.Month.ToString() + "-" + Date.Day.ToString()); | ||||||
|  |         return filename; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private string _removeInvalidChars(string? s) { | ||||||
|  |         if (String.IsNullOrWhiteSpace(s)) return ""; | ||||||
|  |         foreach (var c in Path.GetInvalidFileNameChars()) { | ||||||
|  |             s = s.Replace(c, '-'); | ||||||
|  |         } | ||||||
|  |         return s; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public async void Save(Stream stream) { | ||||||
|  |         var nr = new XElement("opus"); | ||||||
|  |         nr.AddFirst(Root); | ||||||
|  |         await nr.SaveAsync(stream, SaveOptions.DisableFormatting, new CancellationToken()); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										72
									
								
								HaWeb/XMLParser/XMLService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								HaWeb/XMLParser/XMLService.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | namespace HaWeb.XMLParser; | ||||||
|  | using HaWeb.Settings.XMLRoots; | ||||||
|  | using System.Xml.Linq; | ||||||
|  | using Microsoft.AspNetCore.Mvc.ModelBinding; | ||||||
|  |  | ||||||
|  | public class XMLService : IXMLService { | ||||||
|  |     private Dictionary<string, IXMLRoot>? _Roots; | ||||||
|  |  | ||||||
|  |     public XMLService() { | ||||||
|  |         var types = _GetAllTypesThatImplementInterface<IXMLRoot>().ToList(); | ||||||
|  |         types.ForEach( x => { | ||||||
|  |             if (this._Roots == null) this._Roots = new Dictionary<string, IXMLRoot>(); | ||||||
|  |             var instance = (IXMLRoot)Activator.CreateInstance(x)!; | ||||||
|  |             if (instance != null) this._Roots.Add(instance.Type, instance); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public List<IXMLRoot>? GetRoots() => this._Roots == null ? null : this._Roots.Values.ToList(); | ||||||
|  |  | ||||||
|  |     public List<XMLRootDocument>? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState) { | ||||||
|  |         if (document.Root!.Name != "opus") { | ||||||
|  |             ModelState.AddModelError("Error", "A valid Hamann-Docuemnt must begin with <opus>"); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         var res = _testElements(document.Root.Elements()); | ||||||
|  |         if (document.Root.Element("data") != null) { | ||||||
|  |             var datares = _testElements(document.Element("data")!.Elements()); | ||||||
|  |             if (datares != null && res == null) res = datares; | ||||||
|  |             else if (datares != null) res!.AddRange(datares); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private List<XMLRootDocument>? _testElements(IEnumerable<XElement>? elements) { | ||||||
|  |         if (elements == null) return null; | ||||||
|  |         List<XMLRootDocument>? res = null; | ||||||
|  |         foreach (var elem in elements) { | ||||||
|  |            var doc = _testElement(elem); | ||||||
|  |            if (doc != null) { | ||||||
|  |                if (res == null) res = new List<XMLRootDocument>(); | ||||||
|  |                res.Add(doc); | ||||||
|  |            } | ||||||
|  |         } | ||||||
|  |         return res; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private XMLRootDocument? _testElement(XElement? element) { | ||||||
|  |         if (element == null || _Roots == null) return null; | ||||||
|  |         foreach (var (_, root) in _Roots) { | ||||||
|  |             if(root.IsTypeOf(element)) | ||||||
|  |                 return _createXMLRootDocument(root, element); | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element) { | ||||||
|  |         var doc = new XMLRootDocument(Root.Type, Root.GenerateIdentificationString(element), element); | ||||||
|  |         doc.Elements = Root.GetCollectedObjects(doc); | ||||||
|  |         doc.Fields = Root.GenerateFields(doc); | ||||||
|  |         return doc; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private IEnumerable<Type> _GetAllTypesThatImplementInterface<T>() | ||||||
|  |     { | ||||||
|  |         return System.Reflection.Assembly.GetExecutingAssembly() | ||||||
|  |             .GetTypes() | ||||||
|  |             .Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -11,6 +11,7 @@ | |||||||
|     "UpdateService": false |     "UpdateService": false | ||||||
|   }, |   }, | ||||||
|   "AllowedHosts": "*", |   "AllowedHosts": "*", | ||||||
|   "StoredFilesPath": "/home/simon/Downloads/", |   "StoredFilesPathLinux": "/home/simon/Downloads/", | ||||||
|  |   "StoredFilePathWindows": "D:/test/", | ||||||
|   "FileSizeLimit": 52428800 |   "FileSizeLimit": 52428800 | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 schnulller
					schnulller