Added Classes for detecting Root Elements of Hamann-Collections

This commit is contained in:
schnulller
2022-06-02 19:26:43 +02:00
parent 2762a5e310
commit 35ce2034f7
20 changed files with 526 additions and 40 deletions

View File

@@ -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,71 +52,85 @@ 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);
//// 5. Stage: Saving the File(s)
foreach (var doc in docs) {
using (var targetStream = System.IO.File.Create(Path.Combine(_targetFilePath, doc.CreateFilename())))
doc.Save(targetStream);
}
//// 4. Stage: Get Filename for the stageing area return Created(nameof(UploadController), docs);
//// 5. Stage: Saving the File
// // Encode Filename for display
// 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 try
// read the headers for the next section. {
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

View 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;
}
}

View File

@@ -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)

View File

@@ -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>

View File

@@ -0,0 +1,5 @@
namespace HaWeb.Models;
public class UploadViewModel {
public List<(string, string)>? AvailableRoots { get; set; }
}

View File

@@ -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();

View File

@@ -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:

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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"

View 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;
}
}

View 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);
}

View 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());
}
}

View 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);
}
}

View File

@@ -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
} }