mirror of
https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
synced 2025-10-29 17:25: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,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
|
||||||
|
|||||||
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