mirror of
https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
synced 2025-10-28 16:55:32 +00:00
Setup Git Repository Parsing
This commit is contained in:
2
.editorconfig
Normal file
2
.editorconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[*.cs]
|
||||||
|
csharp_new_line_before_open_brace = none
|
||||||
@@ -4,6 +4,7 @@ using HaDocument.Logic;
|
|||||||
using HaDocument.Reactors;
|
using HaDocument.Reactors;
|
||||||
using HaXMLReader.Interfaces;
|
using HaXMLReader.Interfaces;
|
||||||
using HaXMLReader;
|
using HaXMLReader;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace HaDocument
|
namespace HaDocument
|
||||||
{
|
{
|
||||||
@@ -32,6 +33,18 @@ namespace HaDocument
|
|||||||
return GetLibrary();
|
return GetLibrary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ILibrary Create(IHaDocumentOptions Settings, XElement root) {
|
||||||
|
_lib = new IntermediateLibrary();
|
||||||
|
SettingsValidator.Validate(Settings);
|
||||||
|
_settings = Settings;
|
||||||
|
_createReader(root);
|
||||||
|
_createReactors();
|
||||||
|
_reader.Read();
|
||||||
|
_library = _createLibrary();
|
||||||
|
_reader.Dispose();
|
||||||
|
return GetLibrary();
|
||||||
|
}
|
||||||
|
|
||||||
private static void _createReactors() {
|
private static void _createReactors() {
|
||||||
new EditreasonReactor(_reader, _lib, _settings.NormalizeWhitespace);
|
new EditreasonReactor(_reader, _lib, _settings.NormalizeWhitespace);
|
||||||
new HandDefsReactor(_reader, _lib);
|
new HandDefsReactor(_reader, _lib);
|
||||||
@@ -49,6 +62,10 @@ namespace HaDocument
|
|||||||
_reader = new FileReader(_settings.HamannXMLFilePath);
|
_reader = new FileReader(_settings.HamannXMLFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void _createReader(XElement root) {
|
||||||
|
_reader = new XElementReader(root);
|
||||||
|
}
|
||||||
|
|
||||||
private static ILibrary _createLibrary()
|
private static ILibrary _createLibrary()
|
||||||
=> _lib.GetLibrary(_settings);
|
=> _lib.GetLibrary(_settings);
|
||||||
|
|
||||||
|
|||||||
2
HaWeb/.editorconfig
Normal file
2
HaWeb/.editorconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[*.cs]
|
||||||
|
csharp_new_line_before_open_brace = none
|
||||||
@@ -20,265 +20,27 @@ using Microsoft.AspNetCore.Http.Features;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
// Controlling all the API-Endpoints
|
// Controlling all the API-Endpoints
|
||||||
|
[FeatureGate(Features.AdminService)]
|
||||||
public class APIController : Controller {
|
public class APIController : Controller {
|
||||||
|
|
||||||
// DI
|
// DI
|
||||||
private IHaDocumentWrappper _lib;
|
private IHaDocumentWrappper _lib;
|
||||||
private IReaderService _readerService;
|
private readonly IXMLFileProvider _xmlProvider;
|
||||||
private readonly long _fileSizeLimit;
|
|
||||||
private readonly string _targetFilePath;
|
|
||||||
private readonly IXMLService _xmlService;
|
|
||||||
private readonly IXMLProvider _xmlProvider;
|
|
||||||
private readonly IXMLTestService _testService;
|
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
private static readonly string[] _permittedExtensions = { ".xml" };
|
|
||||||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
||||||
|
|
||||||
|
|
||||||
public APIController(IHaDocumentWrappper lib, IReaderService readerService, IXMLService xmlService, IXMLProvider xmlProvider, IXMLTestService testService, IConfiguration config) {
|
public APIController(IHaDocumentWrappper lib, IXMLFileProvider xmlProvider) {
|
||||||
_lib = lib;
|
_lib = lib;
|
||||||
_xmlProvider = xmlProvider;
|
_xmlProvider = xmlProvider;
|
||||||
_readerService = readerService;
|
|
||||||
_xmlService = xmlService;
|
|
||||||
_testService = testService;
|
|
||||||
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
|
||||||
_targetFilePath = config.GetValue<string>("StoredFilePathWindows");
|
|
||||||
} else {
|
|
||||||
_targetFilePath = config.GetValue<string>("StoredFilePathLinux");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("API/Syntaxcheck/{id}")]
|
|
||||||
[DisableFormValueModelBinding]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
[FeatureGate(Features.UploadService, Features.AdminService)]
|
|
||||||
public IActionResult SyntaxCheck(string id) {
|
|
||||||
return Ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
//// UPLOAD ////
|
|
||||||
[HttpPost]
|
|
||||||
[Route("API/Upload")]
|
|
||||||
[DisableFormValueModelBinding]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
[FeatureGate(Features.UploadService, Features.AdminService)]
|
|
||||||
public async Task<IActionResult> Upload() {
|
|
||||||
List<XMLRootDocument>? docs = null;
|
|
||||||
//// 1. Stage: Check Request format and request spec
|
|
||||||
// Checks the Content-Type Field (must be multipart + Boundary)
|
|
||||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) {
|
|
||||||
ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Divides the multipart document into it's sections and sets up a reader
|
|
||||||
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
|
|
||||||
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
|
|
||||||
MultipartSection? section = null;
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (section != null) {
|
|
||||||
// Multipart document content disposition header read for a section:
|
|
||||||
// Starts with boundary, contains field name, content-dispo, filename, content-type
|
|
||||||
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
|
|
||||||
|
|
||||||
if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") {
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasContentDispositionHeader && contentDisposition != null) {
|
|
||||||
// Checks if it is a section with content-disposition, name, filename
|
|
||||||
if (!MultipartRequestHelper.HasFileContentDisposition(contentDisposition)) {
|
|
||||||
ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
//// 2. Stage: Check File. Sanity checks on the file on a byte level, extension checking, is it empty etc.
|
|
||||||
var streamedFileContent = await XMLFileHelpers.ProcessStreamedFile(
|
|
||||||
section, contentDisposition, ModelState,
|
|
||||||
_permittedExtensions, _fileSizeLimit);
|
|
||||||
if (!ModelState.IsValid || streamedFileContent == null)
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
|
|
||||||
//// 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);
|
|
||||||
|
|
||||||
//// 4. Stage: Is it a Hamann-Document? What kind?
|
|
||||||
var retdocs = _xmlService.ProbeFile(xdocument, ModelState);
|
|
||||||
if (!ModelState.IsValid || retdocs == null || !retdocs.Any())
|
|
||||||
return UnprocessableEntity(ModelState);
|
|
||||||
|
|
||||||
//// 5. Stage: Saving the File(s)
|
|
||||||
foreach (var doc in retdocs) {
|
|
||||||
// Physical saving
|
|
||||||
await _xmlProvider.Save(doc, _targetFilePath, ModelState);
|
|
||||||
// Setting the new docuemnt as used
|
|
||||||
_xmlService.Use(doc);
|
|
||||||
// Unsetting all old docuemnts as ununsed
|
|
||||||
_xmlService.AutoUse(doc.Prefix);
|
|
||||||
if (!ModelState.IsValid) return StatusCode(500, ModelState);
|
|
||||||
if (docs == null) docs = new List<XMLRootDocument>();
|
|
||||||
docs.Add(doc);
|
|
||||||
}
|
|
||||||
xdocument = null;
|
|
||||||
retdocs = null;
|
|
||||||
streamedFileContent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Stage: Success! Returning Ok, and redirecting
|
|
||||||
JsonSerializerOptions options = new() {
|
|
||||||
ReferenceHandler = ReferenceHandler.Preserve,
|
|
||||||
Converters = {
|
|
||||||
new IdentificationStringJSONConverter()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
string json = JsonSerializer.Serialize(docs);
|
|
||||||
_testService.Test();
|
|
||||||
return Created(nameof(UploadController), json);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//// PUBLISH ////
|
|
||||||
[HttpPost]
|
|
||||||
[Route("API/LocalPublish")]
|
|
||||||
[DisableFormValueModelBinding]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
[FeatureGate(Features.LocalPublishService, Features.AdminService, Features.UploadService)]
|
|
||||||
public async Task<IActionResult> LocalPublish() {
|
|
||||||
var element = _xmlService.MergeUsedDocuments(ModelState);
|
|
||||||
if (!ModelState.IsValid || element == null)
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
var savedfile = await _xmlProvider.SaveHamannFile(element, _targetFilePath, ModelState);
|
|
||||||
if (!ModelState.IsValid || savedfile == null) {
|
|
||||||
if (savedfile != null)
|
|
||||||
_xmlProvider.DeleteHamannFile(savedfile.Name);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
_ = _lib.SetLibrary(savedfile.PhysicalPath, ModelState);
|
|
||||||
if (!ModelState.IsValid) {
|
|
||||||
_xmlProvider.DeleteHamannFile(savedfile.Name);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
_xmlProvider.SetInProduction(savedfile);
|
|
||||||
_xmlService.SetInProduction();
|
|
||||||
return Created("/", _xmlProvider.GetHamannFiles());
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[Route("API/SetUsed/{id}")]
|
|
||||||
[DisableFormValueModelBinding]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
[FeatureGate(Features.UploadService, Features.AdminService)]
|
|
||||||
public async Task<IActionResult> SetUsed(string id) {
|
|
||||||
var f = _xmlProvider.GetFiles(id);
|
|
||||||
if (f == null) {
|
|
||||||
ModelState.AddModelError("Error", "Wrong Endpoint");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
var files = f.GetFileList();
|
|
||||||
if (files == null) {
|
|
||||||
ModelState.AddModelError("Error", "Wrong Endpoint");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<XMLRootDocument>? newUsed = null;
|
|
||||||
|
|
||||||
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType)) {
|
|
||||||
ModelState.AddModelError("Error", $"Wrong / No Content Type on the Request");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Same as above, check Upload()
|
|
||||||
var boundary = MultipartRequestHelper.GetBoundary(MediaTypeHeaderValue.Parse(Request.ContentType), _defaultFormOptions.MultipartBoundaryLengthLimit);
|
|
||||||
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
|
|
||||||
MultipartSection? section = null;
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (section != null) {
|
|
||||||
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out var contentDisposition);
|
|
||||||
|
|
||||||
if (contentDisposition != null && contentDisposition.Name == "__RequestVerificationToken") {
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filename = string.Empty;
|
|
||||||
if (hasContentDispositionHeader && contentDisposition != null) {
|
|
||||||
if (!MultipartRequestHelper.HasFormDataContentDisposition(contentDisposition)) {
|
|
||||||
ModelState.AddModelError("Error", $"Wrong Content-Dispostion Headers in Multipart Document");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
filename = XMLFileHelpers.StreamToString(section.Body, ModelState);
|
|
||||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
|
||||||
|
|
||||||
var isFile = files.Where(x => x.FileName == filename);
|
|
||||||
if (isFile == null || !isFile.Any()) {
|
|
||||||
ModelState.AddModelError("Error", "Tried to add a file that does not exist.");
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newUsed == null) newUsed = new List<XMLRootDocument>();
|
|
||||||
newUsed.Add(isFile.First());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
section = await reader.ReadNextSectionAsync();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "The Request is bad: " + ex.Message);
|
|
||||||
return BadRequest(ModelState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_xmlService.UnUse(id);
|
|
||||||
if (newUsed != null && newUsed.Any()) {
|
|
||||||
newUsed.ForEach(x => _xmlService.Use(x));
|
|
||||||
}
|
|
||||||
|
|
||||||
_testService.Test();
|
|
||||||
return Created("/", newUsed);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("API/SetInProduction")]
|
[Route("API/SetInProduction")]
|
||||||
[DisableFormValueModelBinding]
|
[DisableFormValueModelBinding]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
[FeatureGate(Features.UploadService, Features.AdminService)]
|
[FeatureGate(Features.LocalPublishService, Features.AdminService)]
|
||||||
public async Task<IActionResult> SetInProduction() {
|
public async Task<IActionResult> SetInProduction() {
|
||||||
var hF = _xmlProvider.GetHamannFiles();
|
var hF = _xmlProvider.GetHamannFiles();
|
||||||
if (hF == null) {
|
if (hF == null) {
|
||||||
@@ -334,31 +96,18 @@ public class APIController : Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (filename == null) {
|
if (filename == null) {
|
||||||
ModelState.AddModelError("Error", "No filename given");
|
ModelState.AddModelError("Error", "Kein Dateiname.");
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newFile = hF.Where(x => x.Name == filename);
|
var newFile = hF.Where(x => x.Name == filename);
|
||||||
if (newFile == null || !newFile.Any()) {
|
if (newFile == null || !newFile.Any()) {
|
||||||
ModelState.AddModelError("Error", "Trying to set a unavailable file.");
|
ModelState.AddModelError("Error", "Versuch, auf eine unverfügbare Datei zuzugreifen.");
|
||||||
return BadRequest(ModelState);
|
return BadRequest(ModelState);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = _lib.SetLibrary(newFile.First().PhysicalPath, ModelState);
|
_ = _lib.SetLibrary(newFile.First(), null, ModelState);
|
||||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
|
||||||
_xmlProvider.SetInProduction(newFile.First());
|
|
||||||
|
|
||||||
return Created("/", newFile.First());
|
return Created("/", newFile.First());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[Route("API/SetYearSetting")]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
[FeatureGate(Features.UploadService, Features.AdminService)]
|
|
||||||
public async Task<IActionResult>? SetEndYear(YearSetting startendyear) {
|
|
||||||
_lib.SetEndYear(startendyear.EndYear);
|
|
||||||
return Created("/", "");;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,6 @@ public class AdminController : Controller {
|
|||||||
[Route("Admin")]
|
[Route("Admin")]
|
||||||
[FeatureGate(Features.AdminService)]
|
[FeatureGate(Features.AdminService)]
|
||||||
public IActionResult Index() {
|
public IActionResult Index() {
|
||||||
return Redirect("/Admin/Upload");
|
return Redirect("/Admin/XMLState");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ public class Briefecontroller : Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static List<(string Sender, string Receiver)> generateSendersRecievers(List<Person>? senders, List<Person>? receivers, bool generatePersonLinks) {
|
private static List<(string Sender, string Receiver)>? generateSendersRecievers(List<Person>? senders, List<Person>? receivers, bool generatePersonLinks) {
|
||||||
var res = new List<(string Sender, string Receiver)>();
|
var res = new List<(string Sender, string Receiver)>();
|
||||||
if (senders == null || receivers == null) return null;
|
if (senders == null || receivers == null) return null;
|
||||||
if (!generatePersonLinks) {
|
if (!generatePersonLinks) {
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ namespace HaWeb.Controllers;
|
|||||||
public class IndexController : Controller {
|
public class IndexController : Controller {
|
||||||
private IHaDocumentWrappper _lib;
|
private IHaDocumentWrappper _lib;
|
||||||
private IReaderService _readerService;
|
private IReaderService _readerService;
|
||||||
private IXMLService _xmlService;
|
private IXMLInteractionService _xmlService;
|
||||||
private int _lettersForPage;
|
private int _lettersForPage;
|
||||||
private int _endYear;
|
private int _endYear;
|
||||||
|
|
||||||
public IndexController(IHaDocumentWrappper lib, IReaderService readerService, IXMLService service, IConfiguration config) {
|
public IndexController(IXMLFileProvider _, IHaDocumentWrappper lib, IReaderService readerService, IXMLInteractionService service, IConfiguration config) {
|
||||||
_lib = lib;
|
_lib = lib;
|
||||||
_readerService = readerService;
|
_readerService = readerService;
|
||||||
_xmlService = service;
|
_xmlService = service;
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ namespace HaWeb.Controllers;
|
|||||||
public class SucheController : Controller {
|
public class SucheController : Controller {
|
||||||
private IHaDocumentWrappper _lib;
|
private IHaDocumentWrappper _lib;
|
||||||
private IReaderService _readerService;
|
private IReaderService _readerService;
|
||||||
private IXMLService _xmlService;
|
private IXMLInteractionService _xmlService;
|
||||||
private int _lettersForPage;
|
private int _lettersForPage;
|
||||||
|
|
||||||
public SucheController(IHaDocumentWrappper lib, IReaderService readerService, IXMLService service, IConfiguration config) {
|
public SucheController(IHaDocumentWrappper lib, IReaderService readerService, IXMLInteractionService service, IConfiguration config) {
|
||||||
_lib = lib;
|
_lib = lib;
|
||||||
_readerService = readerService;
|
_readerService = readerService;
|
||||||
_xmlService = service;
|
_xmlService = service;
|
||||||
@@ -206,7 +206,7 @@ public class SucheController : Controller {
|
|||||||
string activeSearch,
|
string activeSearch,
|
||||||
SearchType ST,
|
SearchType ST,
|
||||||
SearchResultType SRT,
|
SearchResultType SRT,
|
||||||
List<CommentModel> comments) {
|
List<CommentModel>? comments) {
|
||||||
// Model init & return
|
// Model init & return
|
||||||
var model = new SucheViewModel(ST, SRT, null, 0, null, activeSearch, null, null, comments, null);
|
var model = new SucheViewModel(ST, SRT, null, 0, null, activeSearch, null, null, comments, null);
|
||||||
return View("~/Views/HKB/Dynamic/Suche.cshtml", model);
|
return View("~/Views/HKB/Dynamic/Suche.cshtml", model);
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
namespace HaWeb.Controllers;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using HaDocument.Interfaces;
|
|
||||||
using HaXMLReader.Interfaces;
|
|
||||||
using Microsoft.FeatureManagement.Mvc;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using HaWeb.Filters;
|
|
||||||
using HaWeb.XMLParser;
|
|
||||||
using HaWeb.Models;
|
|
||||||
using HaWeb.FileHelpers;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
|
|
||||||
public class UploadController : Controller {
|
|
||||||
// DI
|
|
||||||
private IHaDocumentWrappper _lib;
|
|
||||||
private IReaderService _readerService;
|
|
||||||
private readonly long _fileSizeLimit;
|
|
||||||
private readonly string _targetFilePath;
|
|
||||||
private readonly IXMLService _xmlService;
|
|
||||||
private readonly IXMLProvider _xmlProvider;
|
|
||||||
|
|
||||||
// Options
|
|
||||||
private static readonly string[] _permittedExtensions = { ".xml" };
|
|
||||||
private static readonly FormOptions _defaultFormOptions = new FormOptions();
|
|
||||||
|
|
||||||
|
|
||||||
public UploadController(IHaDocumentWrappper lib, IReaderService readerService, IXMLService xmlService, IXMLProvider xmlProvider, IConfiguration config) {
|
|
||||||
_lib = lib;
|
|
||||||
_readerService = readerService;
|
|
||||||
_xmlService = xmlService;
|
|
||||||
_xmlProvider = xmlProvider;
|
|
||||||
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
|
||||||
_targetFilePath = config.GetValue<string>("StoredFilePathWindows");
|
|
||||||
} else {
|
|
||||||
_targetFilePath = config.GetValue<string>("StoredFilePathLinux");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("Admin/Upload/{id?}")]
|
|
||||||
[FeatureGate(Features.AdminService)]
|
|
||||||
[GenerateAntiforgeryTokenCookie]
|
|
||||||
public IActionResult Index(string? id) {
|
|
||||||
var library = _lib.GetLibrary();
|
|
||||||
var roots = _xmlService.GetRootsList();
|
|
||||||
if (roots == null) return error404();
|
|
||||||
|
|
||||||
var hF = _xmlProvider.GetHamannFiles();
|
|
||||||
List<FileModel>? hamannFiles = null;
|
|
||||||
if (hF != null)
|
|
||||||
hamannFiles = hF
|
|
||||||
.OrderByDescending(x => x.LastModified)
|
|
||||||
.Select(x => new FileModel(x.Name, string.Empty, x.LastModified.LocalDateTime, false, x == _xmlProvider.GetInProduction()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var uF = _xmlService.GetUsedDictionary();
|
|
||||||
var pF = _xmlService.GetInProduction();
|
|
||||||
|
|
||||||
Dictionary<string, List<FileModel>?>? usedFiles = null;
|
|
||||||
if (uF != null) {
|
|
||||||
usedFiles = new Dictionary<string, List<FileModel>?>();
|
|
||||||
foreach (var kv in uF) {
|
|
||||||
if (kv.Value == null) continue;
|
|
||||||
usedFiles.Add(kv.Key, XMLFileHelpers.ToFileModel(kv.Value, pF, uF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<string, List<FileModel>?>? productionFiles = null;
|
|
||||||
if (pF != null) {
|
|
||||||
productionFiles = new Dictionary<string, List<FileModel>?>();
|
|
||||||
foreach (var kv in pF) {
|
|
||||||
if (kv.Value == null) continue;
|
|
||||||
productionFiles.Add(kv.Key, XMLFileHelpers.ToFileModel(kv.Value, pF, uF));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var availableYears = library.MetasByYear.Select(x => x.Key).Union(library.ExcludedMetasByYear.Select(x => x.Key)).ToList();
|
|
||||||
availableYears.Sort();
|
|
||||||
if (id != null) {
|
|
||||||
id = id.ToLower();
|
|
||||||
|
|
||||||
var root = _xmlService.GetRoot(id);
|
|
||||||
if (root == null) return error404();
|
|
||||||
|
|
||||||
var model = new UploadViewModel(root.Type, id, roots, usedFiles, _lib.GetStartYear(), _lib.GetEndYear(), availableYears);
|
|
||||||
model.ProductionFiles = productionFiles;
|
|
||||||
model.HamannFiles = hamannFiles;
|
|
||||||
model.AvailableFiles = XMLFileHelpers.ToFileModel(_xmlProvider.GetFiles(id), pF, uF);
|
|
||||||
|
|
||||||
return View("~/Views/Admin/Dynamic/Upload.cshtml", model);
|
|
||||||
} else {
|
|
||||||
var model = new UploadViewModel("Upload & Veröffentlichen", id, roots, usedFiles, _lib.GetStartYear(), _lib.GetEndYear(), availableYears);
|
|
||||||
model.ProductionFiles = productionFiles;
|
|
||||||
model.HamannFiles = hamannFiles;
|
|
||||||
|
|
||||||
return View("~/Views/Admin/Dynamic/Upload.cshtml", model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IActionResult error404() {
|
|
||||||
Response.StatusCode = 404;
|
|
||||||
return Redirect("/Error404");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
HaWeb/Controllers/XMLStateController.cs
Normal file
50
HaWeb/Controllers/XMLStateController.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace HaWeb.Controllers;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.FeatureManagement.Mvc;
|
||||||
|
using HaWeb.Filters;
|
||||||
|
using HaWeb.XMLParser;
|
||||||
|
using HaWeb.Models;
|
||||||
|
using HaWeb.FileHelpers;
|
||||||
|
using HaWeb.BackgroundTask;
|
||||||
|
|
||||||
|
[FeatureGate(Features.AdminService)]
|
||||||
|
public class XMLStateController : Controller {
|
||||||
|
// DI
|
||||||
|
private IHaDocumentWrappper _lib;
|
||||||
|
private readonly IXMLInteractionService _xmlService;
|
||||||
|
private readonly IXMLFileProvider _xmlProvider;
|
||||||
|
private readonly IMonitorLoop _loop;
|
||||||
|
public XMLStateController(IMonitorLoop loop, IHaDocumentWrappper lib, IXMLInteractionService xmlService, IXMLFileProvider xmlProvider) {
|
||||||
|
_lib = lib;
|
||||||
|
_xmlService = xmlService;
|
||||||
|
_xmlProvider = xmlProvider;
|
||||||
|
_loop = loop;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("Admin/XMLState/")]
|
||||||
|
[FeatureGate(Features.AdminService)]
|
||||||
|
[GenerateAntiforgeryTokenCookie]
|
||||||
|
public IActionResult Index() {
|
||||||
|
_loop.StartMonitorLoop();
|
||||||
|
var library = _lib.GetLibrary();
|
||||||
|
var roots = _xmlService.GetRootsList();
|
||||||
|
if (roots == null) return error404();
|
||||||
|
|
||||||
|
var hF = _xmlProvider.GetHamannFiles()?.OrderByDescending(x => x.LastModified).ToList();
|
||||||
|
var mF = _xmlService.GetManagedFiles();
|
||||||
|
var gD = _xmlProvider.GetGitData();
|
||||||
|
var activeF = _lib.GetActiveFile();
|
||||||
|
var vS = _xmlService.GetValidState();
|
||||||
|
|
||||||
|
var model = new XMLStateViewModel("Dateiübersicht", gD, roots, hF, mF, vS) {
|
||||||
|
ActiveFile = activeF,
|
||||||
|
};
|
||||||
|
return View("~/Views/Admin/Dynamic/XMLState.cshtml", model);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IActionResult error404() {
|
||||||
|
Response.StatusCode = 404;
|
||||||
|
return Redirect("/Error404");
|
||||||
|
}
|
||||||
|
}
|
||||||
85
HaWeb/FileHelpers/ConfigurationMonitor.cs
Normal file
85
HaWeb/FileHelpers/ConfigurationMonitor.cs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
using System.Timers;
|
||||||
|
|
||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
|
||||||
|
public class ConfigurationMonitor {
|
||||||
|
private System.Timers.Timer? _timer;
|
||||||
|
private (string, byte[])[]? _h;
|
||||||
|
private IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public ConfigurationMonitor(string[] paths, IServiceProvider services) {
|
||||||
|
_h = _getHash(paths);
|
||||||
|
_serviceProvider = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (string, byte[])[]? _getHash(string[] paths) {
|
||||||
|
if (paths == null || !paths.Any()) return null;
|
||||||
|
var ret = new List<(string, byte[])>();
|
||||||
|
foreach(var c in paths)
|
||||||
|
ret.Add((c, _computeHash(c)));
|
||||||
|
return ret.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isEqual((string, byte[])[]? _h1, (string, byte[])[]? _h2) {
|
||||||
|
if (_h1 == null && _h2 == null) return true;
|
||||||
|
if (_h1 == null && _h2 != null) return false;
|
||||||
|
if (_h2 == null && _h1 != null) return false;
|
||||||
|
if (_h1!.Count() != _h2!.Count()) return false;
|
||||||
|
foreach (var h1 in _h1!) {
|
||||||
|
foreach (var h2 in _h2!) {
|
||||||
|
if (h1.Item1 == h2.Item1 && !Enumerable.SequenceEqual(h1.Item2, h2.Item2)) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvokeChanged(string[] paths) {
|
||||||
|
var h = _getHash(paths);
|
||||||
|
if (_timer == null && !isEqual(h, _h)) {
|
||||||
|
_h = h;
|
||||||
|
_timer = new(5000) { AutoReset = false };
|
||||||
|
_timer.Enabled = true;
|
||||||
|
_timer.Elapsed += Action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Action(Object source, System.Timers.ElapsedEventArgs e) {
|
||||||
|
Console.WriteLine("Configuration changed (ConfigurationMonitor Class)");
|
||||||
|
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||||
|
IServiceProvider provider = serviceScope.ServiceProvider;
|
||||||
|
|
||||||
|
var cP = provider.GetRequiredService<IConfiguration>();
|
||||||
|
var hP = provider.GetRequiredService<IHaDocumentWrappper>();
|
||||||
|
hP.ParseConfiguration(cP);
|
||||||
|
var fP = provider.GetRequiredService<IXMLFileProvider>();
|
||||||
|
fP.Reload(cP);
|
||||||
|
|
||||||
|
// _lifetime.StopApplication();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] _computeHash(string filePath) {
|
||||||
|
var runCount = 1;
|
||||||
|
|
||||||
|
while(runCount < 4) {
|
||||||
|
try {
|
||||||
|
if (File.Exists(filePath))
|
||||||
|
using (var fs = File.OpenRead(filePath)) {
|
||||||
|
return System.Security.Cryptography.SHA1
|
||||||
|
.Create().ComputeHash(fs);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new FileNotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex) {
|
||||||
|
if (runCount == 3)
|
||||||
|
throw;
|
||||||
|
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(Math.Pow(2, runCount)));
|
||||||
|
runCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new byte[20];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,12 +9,13 @@ using HaXMLReader.Interfaces;
|
|||||||
using HaWeb.SearchHelpers;
|
using HaWeb.SearchHelpers;
|
||||||
using HaWeb.XMLParser;
|
using HaWeb.XMLParser;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
public class HaDocumentWrapper : IHaDocumentWrappper {
|
public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||||
private ILibrary Library;
|
private IFileInfo _ActiveFile;
|
||||||
private IXMLProvider _xmlProvider;
|
private ILibrary? Library;
|
||||||
private IXMLService _xmlService;
|
private IXMLInteractionService _xmlService;
|
||||||
private string _filepath;
|
|
||||||
private int _startYear;
|
private int _startYear;
|
||||||
private int _endYear;
|
private int _endYear;
|
||||||
private List<Person>? _availablePersons;
|
private List<Person>? _availablePersons;
|
||||||
@@ -22,23 +23,14 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
|||||||
|
|
||||||
// public List<SearchHelpers.CollectedItem>? SearchableLetters { get; private set; }
|
// public List<SearchHelpers.CollectedItem>? SearchableLetters { get; private set; }
|
||||||
|
|
||||||
public HaDocumentWrapper(IXMLProvider xmlProvider, IXMLService service, IConfiguration configuration) {
|
public HaDocumentWrapper(IXMLInteractionService service, IConfiguration configuration) {
|
||||||
_xmlProvider = xmlProvider;
|
|
||||||
_xmlService = service;
|
_xmlService = service;
|
||||||
|
ParseConfiguration(configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ParseConfiguration(IConfiguration configuration) {
|
||||||
_startYear = configuration.GetValue<int>("AvailableStartYear");
|
_startYear = configuration.GetValue<int>("AvailableStartYear");
|
||||||
_endYear = configuration.GetValue<int>("AvailableEndYear");
|
_endYear = configuration.GetValue<int>("AvailableEndYear");
|
||||||
var filelist = xmlProvider.GetHamannFiles();
|
|
||||||
if (filelist != null && filelist.Any()) {
|
|
||||||
_AutoLoad(filelist);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use Fallback library
|
|
||||||
if (Library == null) {
|
|
||||||
var options = new HaWeb.Settings.HaDocumentOptions();
|
|
||||||
if (SetLibrary(options.HamannXMLFilePath) == null) {
|
|
||||||
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Person>? GetAvailablePersons() => _availablePersons;
|
public List<Person>? GetAvailablePersons() => _availablePersons;
|
||||||
@@ -49,26 +41,29 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
|||||||
|
|
||||||
public int GetEndYear() => _endYear;
|
public int GetEndYear() => _endYear;
|
||||||
|
|
||||||
public void SetEndYear(int end) {
|
public IFileInfo GetActiveFile() => _ActiveFile;
|
||||||
this._endYear = end;
|
|
||||||
SetLibrary(_filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ILibrary? SetLibrary(string filepath, ModelStateDictionary? ModelState = null) {
|
public ILibrary? SetLibrary(IFileInfo? file, XDocument? doc, ModelStateDictionary? ModelState = null) {
|
||||||
// 1. Set ILibrary
|
// Handle null on file & doc
|
||||||
|
var path = file == null ? new HaWeb.Settings.HaDocumentOptions().HamannXMLFilePath : file.PhysicalPath;
|
||||||
|
if (doc == null) doc = XDocument.Load(path, LoadOptions.PreserveWhitespace);
|
||||||
|
|
||||||
|
// 1. Parse the Document, create search Index
|
||||||
|
if (_xmlService != null)
|
||||||
|
_xmlService.CreateSearchables(doc);
|
||||||
|
// 2. Set ILibrary
|
||||||
try {
|
try {
|
||||||
Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions() { HamannXMLFilePath = filepath, AvailableYearRange = (_startYear, _endYear) });
|
Library = HaDocument.Document.Create(new HaWeb.Settings.HaDocumentOptions() { HamannXMLFilePath = path, AvailableYearRange = (_startYear, _endYear) }, doc.Root);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (ModelState != null) ModelState.AddModelError("Error", "Das Dokument konnte nicht geparst werden: " + ex.Message);
|
if (ModelState != null) ModelState.AddModelError("Error", "Das Dokument konnte nicht geparst werden: " + ex.Message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1a. Set Available Persons
|
// 3a. Set Available Persons
|
||||||
var persons = Library.Metas.SelectMany(x => x.Value.Senders.Union(x.Value.Receivers)).Distinct();
|
var persons = Library.Metas.SelectMany(x => x.Value.Senders.Union(x.Value.Receivers)).Distinct();
|
||||||
_availablePersons = persons.Select(x => Library.Persons[x]).OrderBy(x => x.Surname).ThenBy(x => x.Prename).ToList();
|
_availablePersons = persons.Select(x => Library.Persons[x]).OrderBy(x => x.Surname).ThenBy(x => x.Prename).ToList();
|
||||||
|
|
||||||
// 1b. Setup a Dictionary with available Person ovierview Pages
|
// 3b. Setup a Dictionary with available Person ovierview Pages
|
||||||
|
|
||||||
_personsWithLetters = new Dictionary<string, Person>();
|
_personsWithLetters = new Dictionary<string, Person>();
|
||||||
var availablePersonPages = Library.Persons.Where(x => !String.IsNullOrWhiteSpace(x.Value.Komm));
|
var availablePersonPages = Library.Persons.Where(x => !String.IsNullOrWhiteSpace(x.Value.Komm));
|
||||||
foreach (var p in availablePersonPages) {
|
foreach (var p in availablePersonPages) {
|
||||||
@@ -77,30 +72,12 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Set Library in Production, collect some Objects
|
// 4. Set info on loaded file
|
||||||
if (_xmlService != null)
|
_ActiveFile = file;
|
||||||
_xmlService.SetInProduction(System.Xml.Linq.XDocument.Load(filepath, System.Xml.Linq.LoadOptions.PreserveWhitespace));
|
|
||||||
|
|
||||||
// 3. Set Filepath
|
|
||||||
_filepath = filepath;
|
|
||||||
return Library;
|
return Library;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ILibrary GetLibrary() {
|
public ILibrary? GetLibrary() {
|
||||||
return Library;
|
return Library;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _AutoLoad(List<IFileInfo> files) {
|
|
||||||
var orderdlist = files.OrderByDescending(x => x.LastModified);
|
|
||||||
foreach (var item in orderdlist) {
|
|
||||||
if (SetLibrary(item.PhysicalPath) != null) {
|
|
||||||
_xmlProvider.SetInProduction(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _prepareSearch(HaDocument.Interfaces.ISearchable objecttoseach) {
|
|
||||||
return SearchHelpers.StringHelpers.NormalizeWhiteSpace(objecttoseach.Element, ' ', false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
6
HaWeb/FileHelpers/IConfigurationMonitor.cs
Normal file
6
HaWeb/FileHelpers/IConfigurationMonitor.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
|
||||||
|
public interface IConfigurationMonitor
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,14 +3,16 @@ using HaDocument.Interfaces;
|
|||||||
using HaDocument.Models;
|
using HaDocument.Models;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
using HaXMLReader.Interfaces;
|
using HaXMLReader.Interfaces;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
public interface IHaDocumentWrappper {
|
public interface IHaDocumentWrappper {
|
||||||
public ILibrary? SetLibrary(string filepath, ModelStateDictionary ModelState);
|
public ILibrary? SetLibrary(IFileInfo? file, XDocument? doc, ModelStateDictionary? ModelState);
|
||||||
public ILibrary GetLibrary();
|
public ILibrary? GetLibrary();
|
||||||
|
public void ParseConfiguration(IConfiguration configuration);
|
||||||
public int GetStartYear();
|
public int GetStartYear();
|
||||||
public int GetEndYear();
|
public int GetEndYear();
|
||||||
|
public IFileInfo GetActiveFile();
|
||||||
public List<Person>? GetAvailablePersons();
|
public List<Person>? GetAvailablePersons();
|
||||||
public Dictionary<string, Person>? GetPersonsWithLetters();
|
public Dictionary<string, Person>? GetPersonsWithLetters();
|
||||||
public void SetEndYear(int end);
|
|
||||||
}
|
}
|
||||||
17
HaWeb/FileHelpers/IXMLFileProvider.cs
Normal file
17
HaWeb/FileHelpers/IXMLFileProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using HaWeb.Models;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
|
||||||
|
public interface IXMLFileProvider {
|
||||||
|
public List<IFileInfo>? GetWorkingTreeFiles();
|
||||||
|
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState);
|
||||||
|
public List<IFileInfo>? GetHamannFiles();
|
||||||
|
public (DateTime PullTime, string Hash)? GetGitData();
|
||||||
|
public void Reload(IConfiguration config);
|
||||||
|
public bool HasChanged();
|
||||||
|
public void DeleteHamannFile(string filename);
|
||||||
|
public void Scan();
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace HaWeb.FileHelpers;
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
using HaWeb.Models;
|
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
|
|
||||||
public interface IXMLProvider {
|
|
||||||
public FileList? GetFiles(string prefix);
|
|
||||||
public Task Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState);
|
|
||||||
public Task<IFileInfo?> SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState);
|
|
||||||
public List<IFileInfo>? GetHamannFiles();
|
|
||||||
public IFileInfo? GetInProduction();
|
|
||||||
public void SetInProduction(IFileInfo info);
|
|
||||||
public void DeleteHamannFile(string filename);
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -54,152 +54,54 @@ public static class XMLFileHelpers {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unused as of rn, used to take a file and do the same sanity checks as below
|
// public static List<FileModel>? ToFileModel(FileList? fileList) {
|
||||||
// public static async Task<byte[]> ProcessFormFile<T>(IFormFile formFile, ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit)
|
// if (fileList == null) return null;
|
||||||
// {
|
// var fL = fileList.GetFileList();
|
||||||
// var fieldDisplayName = string.Empty;
|
// if (fL == null) return null;
|
||||||
|
// var ret = new List<FileModel>();
|
||||||
// // Use reflection to obtain the display name for the model
|
// foreach (var f in fL) {
|
||||||
// // property associated with this IFormFile. If a display
|
// if (f.File == null) continue;
|
||||||
// // name isn't found, error messages simply won't show
|
// ret.Add(ToFileModel(f));
|
||||||
// // a display name.
|
// };
|
||||||
// MemberInfo property =
|
// return ret.OrderBy(x => x.LastModified).ToList();
|
||||||
// typeof(T).GetProperty(
|
|
||||||
// formFile.Name.Substring(formFile.Name.IndexOf(".",
|
|
||||||
// StringComparison.Ordinal) + 1));
|
|
||||||
|
|
||||||
// if (property != null)
|
|
||||||
// {
|
|
||||||
// if (property.GetCustomAttribute(typeof(DisplayAttribute)) is
|
|
||||||
// DisplayAttribute displayAttribute)
|
|
||||||
// {
|
|
||||||
// fieldDisplayName = $"{displayAttribute.Name} ";
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Don't trust the file name sent by the client. To display
|
|
||||||
// // the file name, HTML-encode the value.
|
|
||||||
// var trustedFileNameForDisplay = WebUtility.HtmlEncode(
|
|
||||||
// formFile.FileName);
|
|
||||||
|
|
||||||
// // Check the file length. This check doesn't catch files that only have
|
|
||||||
// // a BOM as their content.
|
|
||||||
// if (formFile.Length == 0)
|
|
||||||
// {
|
|
||||||
// modelState.AddModelError(formFile.Name,
|
|
||||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
|
||||||
|
|
||||||
// return Array.Empty<byte>();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (formFile.Length > sizeLimit)
|
|
||||||
// {
|
|
||||||
// var megabyteSizeLimit = sizeLimit / 1048576;
|
|
||||||
// modelState.AddModelError(formFile.Name,
|
|
||||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
|
|
||||||
// $"{megabyteSizeLimit:N1} MB.");
|
|
||||||
|
|
||||||
// return Array.Empty<byte>();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// using (var memoryStream = new MemoryStream())
|
|
||||||
// {
|
|
||||||
// await formFile.CopyToAsync(memoryStream);
|
|
||||||
|
|
||||||
// // Check the content length in case the file's only
|
|
||||||
// // content was a BOM and the content is actually
|
|
||||||
// // empty after removing the BOM.
|
|
||||||
// if (memoryStream.Length == 0)
|
|
||||||
// {
|
|
||||||
// modelState.AddModelError(formFile.Name,
|
|
||||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!IsValidFileExtensionAndSignature(
|
|
||||||
// formFile.FileName, memoryStream, permittedExtensions))
|
|
||||||
// {
|
|
||||||
// modelState.AddModelError(formFile.Name,
|
|
||||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) file " +
|
|
||||||
// "type isn't permitted or the file's signature " +
|
|
||||||
// "doesn't match the file's extension.");
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// return memoryStream.ToArray();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception ex)
|
|
||||||
// {
|
|
||||||
// modelState.AddModelError(formFile.Name,
|
|
||||||
// $"{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
|
|
||||||
// $"Please contact the Help Desk for support. Error: {ex.HResult}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Array.Empty<byte>();
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public static List<FileModel>? ToFileModel(FileList? fileList, Dictionary<string, FileList?>? productionFiles = null, Dictionary<string, FileList?>? usedFiles = null) {
|
// // TODO: File State IsValid
|
||||||
if (fileList == null) return null;
|
// public static FileModel ToFileModel(XMLRootDocument document) {
|
||||||
var fL = fileList.GetFileList();
|
// string id = document.Prefix;
|
||||||
if (fL == null) return null;
|
// var model = new FileModel(document.FileName, document.File.LastModified.LocalDateTime, true) {
|
||||||
var ret = new List<FileModel>();
|
// Fields = document.Fields,
|
||||||
foreach (var f in fL) {
|
// Messages = document.GetLog(),
|
||||||
if (f.File == null) continue;
|
// Prefix = id
|
||||||
ret.Add(ToFileModel(f, productionFiles, usedFiles));
|
// };
|
||||||
};
|
// return model;
|
||||||
return ret.OrderBy(x => x.LastModified).ToList();
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
public static FileModel ToFileModel(XMLRootDocument document, Dictionary<string, FileList?>? productionFiles = null, Dictionary<string, FileList?>? usedFiles = null) {
|
public static bool ProcessFile(
|
||||||
string id = document.Prefix;
|
Stream file,
|
||||||
|
string fileName,
|
||||||
bool inProduction = false;
|
StringBuilder errorMessages,
|
||||||
if (productionFiles != null && productionFiles.ContainsKey(id)) {
|
string[] permittedExtensions,
|
||||||
inProduction = productionFiles[id]!.Contains(document);
|
long sizeLimit) {
|
||||||
}
|
|
||||||
|
|
||||||
bool isUsed = false;
|
|
||||||
if (usedFiles != null && usedFiles.ContainsKey(id)) {
|
|
||||||
isUsed = usedFiles[id]!.Contains(document);
|
|
||||||
}
|
|
||||||
|
|
||||||
var model = new FileModel(document.FileName, document.Prefix, document.File.LastModified.LocalDateTime, isUsed, inProduction) { Fields = document.Fields };
|
|
||||||
model.Messages = document.GetLog();
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<byte[]?> ProcessStreamedFile(
|
|
||||||
MultipartSection section, ContentDispositionHeaderValue contentDisposition,
|
|
||||||
ModelStateDictionary modelState, string[] permittedExtensions, long sizeLimit) {
|
|
||||||
try {
|
try {
|
||||||
using (var memoryStream = new MemoryStream()) {
|
// Check if the file is empty or exceeds the size limit.
|
||||||
await section.Body.CopyToAsync(memoryStream);
|
if (file.Length == 0) {
|
||||||
|
errorMessages.AppendLine("Die Datei ist leer.");
|
||||||
// Check if the file is empty or exceeds the size limit.
|
return false;
|
||||||
if (memoryStream.Length == 0)
|
}
|
||||||
modelState.AddModelError("Error", "The file is empty.");
|
else if (file.Length > sizeLimit) {
|
||||||
else if (memoryStream.Length > sizeLimit) {
|
var megabyteSizeLimit = sizeLimit / 1048576;
|
||||||
var megabyteSizeLimit = sizeLimit / 1048576;
|
errorMessages.AppendLine($"Die Datei überschreitet das Größenlimit {megabyteSizeLimit:N1} MB.");
|
||||||
modelState.AddModelError("Error", $"The file exceeds {megabyteSizeLimit:N1} MB.");
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// Check file extension and first bytes
|
|
||||||
else if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, memoryStream, permittedExtensions))
|
|
||||||
modelState.AddModelError("Error", "The file must be of the following specs:<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\"?>");
|
|
||||||
|
|
||||||
// Return the File as a byte array
|
|
||||||
else return memoryStream.ToArray();
|
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
|
||||||
modelState.AddModelError("Error", $"The upload failed. Error: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
// Return orderly, if signature & extension okay
|
||||||
|
else return IsValidFileExtensionAndSignature(fileName, file, errorMessages, permittedExtensions);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
errorMessages.AppendLine($"The upload failed. Error: {ex.Message}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string? StreamToString(System.IO.Stream stream, ModelStateDictionary modelState) {
|
public static string? StreamToString(System.IO.Stream stream, ModelStateDictionary modelState) {
|
||||||
@@ -216,22 +118,26 @@ public static class XMLFileHelpers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, string[] permittedExtensions) {
|
private static bool IsValidFileExtensionAndSignature(string fileName, Stream data, StringBuilder errorMessages, string[] permittedExtensions) {
|
||||||
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
|
if (string.IsNullOrEmpty(fileName) || data == null || data.Length == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
var ext = Path.GetExtension(fileName).ToLowerInvariant();
|
||||||
|
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext)) {
|
||||||
if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
|
errorMessages.AppendLine("Dateiname endet nicht auf .xml");
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
data.Position = 0;
|
data.Position = 0;
|
||||||
|
|
||||||
using (var reader = new BinaryReader(data)) {
|
using (var reader = new BinaryReader(data)) {
|
||||||
var signatures = _fileSignature[ext];
|
var signatures = _fileSignature[ext];
|
||||||
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
|
var headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
|
||||||
return signatures.Any(signature =>
|
if (!signatures.Any(signature =>
|
||||||
headerBytes.Take(signature.Length).SequenceEqual(signature));
|
headerBytes.Take(signature.Length).SequenceEqual(signature))) {
|
||||||
|
errorMessages.AppendLine("Datei muss mit <?xml version=\"1.0\" encoding=\"utf-8\"?> oder <?xml version=\"1.0\"?> beginnen.");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
228
HaWeb/FileHelpers/XMLFileProvider.cs
Normal file
228
HaWeb/FileHelpers/XMLFileProvider.cs
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
namespace HaWeb.FileHelpers;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||||
|
using HaWeb.Models;
|
||||||
|
using HaWeb.XMLParser;
|
||||||
|
using HaWeb.XMLTests;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
||||||
|
public class XMLFileProvider : IXMLFileProvider {
|
||||||
|
private readonly IHaDocumentWrappper _Lib;
|
||||||
|
private readonly IXMLInteractionService _XMLService;
|
||||||
|
|
||||||
|
private IFileProvider _hamannFileProvider;
|
||||||
|
private IFileProvider _bareRepositoryFileProvider;
|
||||||
|
private IFileProvider _workingTreeFileProvider;
|
||||||
|
|
||||||
|
private string _Branch;
|
||||||
|
|
||||||
|
private List<IFileInfo>? _WorkingTreeFiles;
|
||||||
|
private List<IFileInfo>? _HamannFiles;
|
||||||
|
|
||||||
|
private static (DateTime PullTime, string Hash)? _GitData;
|
||||||
|
|
||||||
|
// Startup (LAST)
|
||||||
|
public XMLFileProvider(IXMLInteractionService xmlservice, IHaDocumentWrappper _lib, IConfiguration config) {
|
||||||
|
// TODO: Test Read / Write Access
|
||||||
|
_Lib = _lib;
|
||||||
|
_XMLService = xmlservice;
|
||||||
|
|
||||||
|
_Branch = config.GetValue<string>("RepositoryBranch");
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreWindows"));
|
||||||
|
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathWindows"));
|
||||||
|
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathWindows"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreLinux"));
|
||||||
|
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathLinux"));
|
||||||
|
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathLinux"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
||||||
|
Scan();
|
||||||
|
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||||
|
xmlservice.Collect(_WorkingTreeFiles);
|
||||||
|
}
|
||||||
|
_HamannFiles = _ScanHamannFiles();
|
||||||
|
|
||||||
|
// Check if hamann file already is current working tree status
|
||||||
|
// -> YES: Load up the file via _lib.SetLibrary();
|
||||||
|
if (_IsAlreadyParsed()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> NO: Try to create a new file
|
||||||
|
var created = xmlservice.TryCreate();
|
||||||
|
if (created != null) {
|
||||||
|
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
||||||
|
if (file != null) {
|
||||||
|
_lib.SetLibrary(file, created.Document, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It failed, so use the last best File:
|
||||||
|
else if (_HamannFiles != null && _HamannFiles.Any()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> There is none? Use Fallback:
|
||||||
|
else {
|
||||||
|
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||||
|
if (_lib.SetLibrary(null, null, null) == null) {
|
||||||
|
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reload(IConfiguration config) {
|
||||||
|
_Branch = config.GetValue<string>("RepositoryBranch");
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreWindows"));
|
||||||
|
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathWindows"));
|
||||||
|
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathWindows"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_hamannFileProvider = new PhysicalFileProvider(config.GetValue<string>("HamannFileStoreLinux"));
|
||||||
|
_bareRepositoryFileProvider = new PhysicalFileProvider(config.GetValue<string>("BareRepositoryPathLinux"));
|
||||||
|
_workingTreeFileProvider = new PhysicalFileProvider(config.GetValue<string>("WorkingTreePathLinux"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create File Lists; Here and in xmlservice, which does preliminary checking
|
||||||
|
Scan();
|
||||||
|
if (_WorkingTreeFiles != null && _WorkingTreeFiles.Any()) {
|
||||||
|
_XMLService.Collect(_WorkingTreeFiles);
|
||||||
|
}
|
||||||
|
_HamannFiles = _ScanHamannFiles();
|
||||||
|
|
||||||
|
// Check if hamann file already is current working tree status
|
||||||
|
// -> YES: Load up the file via _lib.SetLibrary();
|
||||||
|
if (_IsAlreadyParsed()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> NO: Try to create a new file
|
||||||
|
var created = _XMLService.TryCreate();
|
||||||
|
if (created != null) {
|
||||||
|
var file = SaveHamannFile(created, _hamannFileProvider.GetFileInfo("./").PhysicalPath, null);
|
||||||
|
if (file != null) {
|
||||||
|
_Lib.SetLibrary(file, created.Document, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// It failed, so use the last best File:
|
||||||
|
else if (_HamannFiles != null && _HamannFiles.Any()) {
|
||||||
|
_Lib.SetLibrary(_HamannFiles.First(), null, null);
|
||||||
|
if (_Lib.GetLibrary() != null) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> There is none? Use Fallback:
|
||||||
|
else {
|
||||||
|
var options = new HaWeb.Settings.HaDocumentOptions();
|
||||||
|
if (_Lib.SetLibrary(null, null, null) == null) {
|
||||||
|
throw new Exception("Die Fallback Hamann.xml unter " + options.HamannXMLFilePath + " kann nicht geparst werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public List<IFileInfo>? GetWorkingTreeFiles() => _WorkingTreeFiles;
|
||||||
|
|
||||||
|
public (DateTime PullTime, string Hash)? GetGitData() => _GitData;
|
||||||
|
|
||||||
|
public List<IFileInfo>? GetHamannFiles() => this._HamannFiles;
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
public void DeleteHamannFile(string filename) {
|
||||||
|
if (_HamannFiles == null) return;
|
||||||
|
var files = _HamannFiles.Where(x => x.Name == filename);
|
||||||
|
foreach (var file in files) {
|
||||||
|
File.Delete(file.PhysicalPath);
|
||||||
|
}
|
||||||
|
_HamannFiles.RemoveAll(x => x.Name == filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Scan() {
|
||||||
|
_WorkingTreeFiles = _ScanWorkingTreeFiles();
|
||||||
|
_GitData = _ScanGitData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileInfo? SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary? ModelState) {
|
||||||
|
if (!_GitData.HasValue) return null;
|
||||||
|
var filename = "hamann_" + _GitData.Value.PullTime.Year + "-" + _GitData.Value.PullTime.Month + "-" + _GitData.Value.PullTime.Day + "_" + _GitData.Value.PullTime.Hour + "-" + _GitData.Value.PullTime.Minute + "." + _GitData.Value.Hash.Substring(0,7) + ".xml";
|
||||||
|
var path = Path.Combine(basefilepath, filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Directory.Exists(basefilepath))
|
||||||
|
Directory.CreateDirectory(basefilepath);
|
||||||
|
using (var targetStream = System.IO.File.Create(path))
|
||||||
|
element.Save(targetStream, SaveOptions.DisableFormatting);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (ModelState != null) ModelState.AddModelError("Error", "Die Datei konnte nicht gespeichert werden: " + ex.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = _hamannFileProvider.GetFileInfo(filename);
|
||||||
|
if (info == null) {
|
||||||
|
if (ModelState != null) ModelState.AddModelError("Error", "Auf die neu erstellte Datei konnte nicht zugegriffen werden.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_HamannFiles == null) _HamannFiles = new List<IFileInfo>();
|
||||||
|
_HamannFiles.RemoveAll(x => x.Name == info.Name);
|
||||||
|
_HamannFiles.Add(info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasChanged() {
|
||||||
|
if (!_GitData.HasValue) return true;
|
||||||
|
var current = _ScanGitData();
|
||||||
|
if (current.Item2 != _GitData.Value.Hash) {
|
||||||
|
_GitData = current;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (DateTime, string) _ScanGitData() {
|
||||||
|
var head = _bareRepositoryFileProvider.GetFileInfo("refs/heads/" + _Branch);
|
||||||
|
return (head.LastModified.DateTime, File.ReadAllText(head.PhysicalPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void _RegisterChangeCallbacks() {
|
||||||
|
var cT = _bareRepositoryFileProvider.Watch("refs/heads/" + _Branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gets all XML Files
|
||||||
|
private List<IFileInfo>? _ScanWorkingTreeFiles() {
|
||||||
|
var files = _workingTreeFileProvider.GetDirectoryContents(string.Empty)!.Where(x => !x.IsDirectory && x.Name.EndsWith(".xml"))!.ToList();
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IFileInfo>? _ScanHamannFiles() {
|
||||||
|
var files = _hamannFileProvider.GetDirectoryContents(string.Empty).Where(x => !x.IsDirectory && x.Name.StartsWith("hamann") && x.Name.EndsWith(".xml"));
|
||||||
|
if (files == null || !files.Any()) return null;
|
||||||
|
return files.OrderByDescending(x => x.LastModified).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _GetHashFromHamannFilename(string filename) {
|
||||||
|
var s = filename.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
if (s.Length != 3 || s.Last() != "xml" || !s.First().StartsWith("hamann")) return null;
|
||||||
|
return s[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _IsAlreadyParsed() {
|
||||||
|
if (_HamannFiles == null || !_HamannFiles.Any() || !_GitData.HasValue) return false;
|
||||||
|
var fhash = _GetHashFromHamannFilename(_HamannFiles.First().Name);
|
||||||
|
var ghash = _GitData.Value.Hash.Substring(0,7);
|
||||||
|
return fhash == ghash;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
namespace HaWeb.FileHelpers;
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using HaWeb.Models;
|
|
||||||
using HaWeb.XMLParser;
|
|
||||||
using HaWeb.XMLTests;
|
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
// XMLProvider provides a wrapper around the available XML data on a FILE basis
|
|
||||||
public class XMLProvider : IXMLProvider {
|
|
||||||
private IFileProvider _fileProvider;
|
|
||||||
private Dictionary<string, FileList?>? _Files;
|
|
||||||
private Dictionary<string, IXMLRoot>? _Roots;
|
|
||||||
private List<IFileInfo>? _HamannFiles;
|
|
||||||
private Stack<IFileInfo>? _InProduction;
|
|
||||||
|
|
||||||
public XMLProvider(IFileProvider provider, IXMLService xmlservice, IXMLTestService testService) {
|
|
||||||
_fileProvider = provider;
|
|
||||||
_Roots = xmlservice.GetRootsDictionary();
|
|
||||||
_Files = _ScanFiles();
|
|
||||||
_HamannFiles = _ScanHamannFiles();
|
|
||||||
|
|
||||||
if (_Files != null)
|
|
||||||
foreach (var category in _Files)
|
|
||||||
if (category.Value != null)
|
|
||||||
xmlservice.AutoUse(category.Value);
|
|
||||||
|
|
||||||
testService.Test();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<IFileInfo>? GetHamannFiles() => this._HamannFiles;
|
|
||||||
|
|
||||||
public IFileInfo? GetInProduction() {
|
|
||||||
if (_InProduction == null || !_InProduction.Any()) return null;
|
|
||||||
return this._InProduction.Peek();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteHamannFile(string filename) {
|
|
||||||
if (_HamannFiles == null) return;
|
|
||||||
var files = _HamannFiles.Where(x => x.Name == filename);
|
|
||||||
foreach (var file in files) {
|
|
||||||
File.Delete(file.PhysicalPath);
|
|
||||||
}
|
|
||||||
_HamannFiles.RemoveAll(x => x.Name == filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInProduction(IFileInfo info) {
|
|
||||||
if (_InProduction == null) _InProduction = new Stack<IFileInfo>();
|
|
||||||
_InProduction.Push(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileList? GetFiles(string prefix)
|
|
||||||
=> _Files != null && _Files.ContainsKey(prefix) ? _Files[prefix] : null;
|
|
||||||
|
|
||||||
// Saves a Document as file and adds it to the collection
|
|
||||||
public async Task Save(XMLRootDocument doc, string basefilepath, ModelStateDictionary ModelState) {
|
|
||||||
var type = doc.Prefix;
|
|
||||||
var directory = Path.Combine(basefilepath, type);
|
|
||||||
var path = Path.Combine(directory, doc.FileName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!Directory.Exists(directory))
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
using (var targetStream = System.IO.File.Create(path))
|
|
||||||
await doc.Save(targetStream, ModelState);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "Speichern der Datei fehlgeschlagen: " + ex.Message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = _fileProvider.GetFileInfo(Path.Combine(doc.Prefix, doc.FileName));
|
|
||||||
if (info == null) {
|
|
||||||
ModelState.AddModelError("Error", "Auf die neu erstellte Datei konnte nicht zugegriffen werden.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.File = info;
|
|
||||||
|
|
||||||
if (_Files == null) _Files = new Dictionary<string, FileList?>();
|
|
||||||
if (!_Files.ContainsKey(doc.Prefix)) _Files.Add(doc.Prefix, new FileList(doc.XMLRoot));
|
|
||||||
_Files[doc.Prefix]!.Add(doc);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IFileInfo?> SaveHamannFile(XElement element, string basefilepath, ModelStateDictionary ModelState) {
|
|
||||||
var date = DateTime.Now;
|
|
||||||
var filename = "hamann_" + date.Year + "-" + date.Month + "-" + date.Day + "." + Path.GetRandomFileName() + ".xml";
|
|
||||||
var directory = Path.Combine(basefilepath, "hamann");
|
|
||||||
var path = Path.Combine(directory, filename);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!Directory.Exists(directory))
|
|
||||||
Directory.CreateDirectory(directory);
|
|
||||||
using (var targetStream = System.IO.File.Create(path))
|
|
||||||
await element.SaveAsync(targetStream, SaveOptions.DisableFormatting, new CancellationToken());
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ModelState.AddModelError("Error", "Die Datei konnte nicht gespeichert werden: " + ex.Message);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = _fileProvider.GetFileInfo(Path.Combine("hamann", filename));
|
|
||||||
if (info == null) {
|
|
||||||
ModelState.AddModelError("Error", "Auf die neu erstellte Dtaei konnte nicht zugegriffen werden.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_HamannFiles == null) _HamannFiles = new List<IFileInfo>();
|
|
||||||
_HamannFiles.RemoveAll(x => x.Name == info.Name);
|
|
||||||
_HamannFiles.Add(info);
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Dictionary<string, FileList?>? _ScanFiles() {
|
|
||||||
if (_Roots == null) return null;
|
|
||||||
Dictionary<string, FileList?>? res = null;
|
|
||||||
var dirs = _fileProvider.GetDirectoryContents(string.Empty).Where(x => x.IsDirectory);
|
|
||||||
foreach (var dir in dirs) {
|
|
||||||
if (_Roots.ContainsKey(dir.Name)) {
|
|
||||||
if (_Files == null) _Files = new Dictionary<string, FileList?>();
|
|
||||||
if (res == null) res = new Dictionary<string, FileList?>();
|
|
||||||
res.Add(dir.Name, _ScanFiles(dir.Name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private FileList? _ScanFiles(string prefix) {
|
|
||||||
if (_Roots == null) return null;
|
|
||||||
FileList? res = null;
|
|
||||||
var files = _fileProvider.GetDirectoryContents(prefix).Where(x => !x.IsDirectory && x.Name.StartsWith(prefix) && x.Name.EndsWith(".xml"));
|
|
||||||
foreach (var file in files) {
|
|
||||||
if (_Roots == null || !_Roots.ContainsKey(prefix))
|
|
||||||
throw new Exception("Attempting to read a File from an unrecognized Prefix: " + prefix);
|
|
||||||
if (res == null) res = new FileList(_Roots[prefix]);
|
|
||||||
res.Add(new XMLRootDocument(_Roots[prefix], file));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<IFileInfo>? _ScanHamannFiles() {
|
|
||||||
var dir = _fileProvider.GetDirectoryContents(string.Empty).Where(x => x.IsDirectory && x.Name == "hamann");
|
|
||||||
if (dir == null || !dir.Any()) return null;
|
|
||||||
var files = _fileProvider.GetDirectoryContents(dir.First().Name).Where(x => !x.IsDirectory && x.Name.StartsWith("hamann") && x.Name.EndsWith(".xml"));
|
|
||||||
if (files == null || !files.Any()) return null;
|
|
||||||
return files.ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,6 @@ public class FileList {
|
|||||||
throw new Exception("Diese Liste kann nur Elemente des Typs " + XMLRoot.Prefix + " enthalten");
|
throw new Exception("Diese Liste kann nur Elemente des Typs " + XMLRoot.Prefix + " enthalten");
|
||||||
|
|
||||||
if (_Files == null) _Files = new HashSet<XMLRootDocument>();
|
if (_Files == null) _Files = new HashSet<XMLRootDocument>();
|
||||||
var replacing = _Files.Where(x => x.FileName == document.FileName);
|
|
||||||
if (replacing != null && replacing.Any()) _Files.Remove(replacing.First());
|
|
||||||
_Files.Add(document);
|
_Files.Add(document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,46 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
namespace HaWeb.Models;
|
namespace HaWeb.Models;
|
||||||
|
|
||||||
public class FileModel {
|
public class FileModel {
|
||||||
public string FileName { get; private set; }
|
public string FileName { get; private set; }
|
||||||
public string Prefix { get; private set; }
|
public IFileInfo File { get; private set; }
|
||||||
public DateTime LastModified { get; private set; }
|
|
||||||
public bool IsUsed { get; private set; }
|
|
||||||
public bool InProduction { get; private set; }
|
|
||||||
public List<(string, string?)>? Fields { get; set; }
|
|
||||||
public string? Messages { get; set; }
|
|
||||||
|
|
||||||
public FileModel(string name, string prefix, DateTime lastModified, bool isUsed, bool inProduction) {
|
// This affects only repo files
|
||||||
|
public bool IsValid { get; private set; } = false;
|
||||||
|
public List<XMLRootDocument>? Content { get; set; }
|
||||||
|
public List<(string, string?)>? Fields { get; set; }
|
||||||
|
public string? Prefix { get; set; }
|
||||||
|
|
||||||
|
private StringBuilder? _log;
|
||||||
|
|
||||||
|
public FileModel(string name, IFileInfo file) {
|
||||||
FileName = name;
|
FileName = name;
|
||||||
IsUsed = isUsed;
|
File = file;
|
||||||
LastModified = lastModified;
|
}
|
||||||
InProduction = inProduction;
|
|
||||||
Prefix = prefix;
|
public string? GetLog() {
|
||||||
|
if (_log == null) return null;
|
||||||
|
return _log.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string msg) {
|
||||||
|
if (_log == null) _log = new StringBuilder();
|
||||||
|
var prefix = DateTime.Now.ToShortTimeString() + " ";
|
||||||
|
if (File != null) prefix += File.Name + ": ";
|
||||||
|
_log.AppendLine(prefix + msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetLog() {
|
||||||
|
if (_log != null) _log.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Validate() {
|
||||||
|
IsValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime GetLastModified() {
|
||||||
|
return File.LastModified.ToLocalTime().DateTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ using System.Collections.Generic;
|
|||||||
public class SearchResult {
|
public class SearchResult {
|
||||||
public string Search { get; private set; }
|
public string Search { get; private set; }
|
||||||
public string Index { get; private set; }
|
public string Index { get; private set; }
|
||||||
public string Identifier { get; set; }
|
public string? Identifier { get; set; }
|
||||||
public string? Page { get; set; }
|
public string? Page { get; set; }
|
||||||
public string? Line { get; set; }
|
public string? Line { get; set; }
|
||||||
public string? Preview { get; set; }
|
public string? Preview { get; set; }
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
namespace HaWeb.Models;
|
|
||||||
using HaWeb.XMLParser;
|
|
||||||
using Microsoft.Extensions.FileProviders;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
|
||||||
|
|
||||||
public class UploadViewModel {
|
|
||||||
public string ActiveTitle { get; private set; }
|
|
||||||
public string? Prefix { get; private set; }
|
|
||||||
public List<IXMLRoot>? AvailableRoots { get; private set; }
|
|
||||||
public List<FileModel>? AvailableFiles { get; set; }
|
|
||||||
public Dictionary<string, List<FileModel>?>? UsedFiles { get; private set; }
|
|
||||||
public Dictionary<string, List<FileModel>?>? ProductionFiles { get; set; }
|
|
||||||
public List<int> AvailableYears { get; private set; }
|
|
||||||
public int StartYear { get; private set; }
|
|
||||||
public int EndYear { get; private set; }
|
|
||||||
public List<FileModel>? HamannFiles { get; set; }
|
|
||||||
|
|
||||||
public UploadViewModel(string title, string? prefix, List<IXMLRoot>? roots, Dictionary<string, List<FileModel>?>? usedFiles, int startYear, int endYear, List<int> availableYears) {
|
|
||||||
Prefix = prefix;
|
|
||||||
ActiveTitle = title;
|
|
||||||
AvailableRoots = roots;
|
|
||||||
UsedFiles = usedFiles;
|
|
||||||
StartYear = startYear;
|
|
||||||
EndYear = endYear;
|
|
||||||
AvailableYears = availableYears;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,154 +7,26 @@ using HaWeb.XMLParser;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
public class XMLRootDocument {
|
public class XMLRootDocument {
|
||||||
private XElement? _Element;
|
[JsonIgnore]
|
||||||
private string? _filename;
|
public XElement? Element { get; private set; }
|
||||||
private IFileInfo? _file;
|
|
||||||
private StringBuilder? _log;
|
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public IXMLRoot XMLRoot { get; private set; }
|
public IXMLRoot XMLRoot { get; private set; }
|
||||||
|
public FileModel File { get; private set; }
|
||||||
|
|
||||||
public string FileName {
|
|
||||||
get {
|
|
||||||
if (_filename == null)
|
|
||||||
_filename = _CreateFilename();
|
|
||||||
return _filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonIgnore]
|
|
||||||
public IFileInfo? File {
|
|
||||||
get {
|
|
||||||
return _file;
|
|
||||||
}
|
|
||||||
set {
|
|
||||||
_file = value;
|
|
||||||
_Element = null;
|
|
||||||
} }
|
|
||||||
public string Prefix { get; private set; }
|
public string Prefix { get; private set; }
|
||||||
public DateTime Date { get; private set; }
|
// UNUSED AS OF NOW
|
||||||
|
|
||||||
public (string?, string?) IdentificationString { get; private set; }
|
public (string?, string?) IdentificationString { get; private set; }
|
||||||
|
// TODO: Fields
|
||||||
public List<(string, string?)>? Fields { get; set; }
|
public List<(string, string?)>? Fields { get; set; }
|
||||||
|
|
||||||
// Entry point for file reading
|
// Entry point for XML file reading
|
||||||
public XMLRootDocument(IXMLRoot xmlRoot, IFileInfo file) {
|
public XMLRootDocument(IXMLRoot xmlRoot, string prefix, (string?, string?) idString, XElement element, FileModel file) {
|
||||||
XMLRoot = xmlRoot;
|
|
||||||
Prefix = xmlRoot.Prefix;
|
|
||||||
File = file;
|
|
||||||
Date = file.LastModified.LocalDateTime;
|
|
||||||
_filename = file.Name;
|
|
||||||
_GenerateFieldsFromFilename(file.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry point for XML upload reading
|
|
||||||
public XMLRootDocument(IXMLRoot xmlRoot, string prefix, (string?, string?) idString, XElement element) {
|
|
||||||
XMLRoot = xmlRoot;
|
XMLRoot = xmlRoot;
|
||||||
Prefix = prefix;
|
Prefix = prefix;
|
||||||
IdentificationString = idString;
|
IdentificationString = idString;
|
||||||
Date = DateTime.Now;
|
File = file;
|
||||||
_Element = element;
|
Element = element;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _CreateFilename() {
|
|
||||||
var filename = _removeInvalidChars(Prefix) + "_";
|
|
||||||
if (!String.IsNullOrWhiteSpace(IdentificationString.Item1)) {
|
|
||||||
var hash = IdentificationString.Item1.GetHashCode().ToString("X8");
|
|
||||||
filename += hash + "_";
|
|
||||||
}
|
|
||||||
if (!String.IsNullOrWhiteSpace(IdentificationString.Item2)) filename += _removeInvalidChars(IdentificationString.Item2) + "_";
|
|
||||||
filename += _removeInvalidChars(Date.Year.ToString() + "-" + Date.Month.ToString() + "-" + Date.Day.ToString()) + "." + Path.GetRandomFileName();
|
|
||||||
return filename + ".xml";
|
|
||||||
}
|
|
||||||
|
|
||||||
private string _removeInvalidChars(string? s) {
|
|
||||||
if (String.IsNullOrWhiteSpace(s)) return string.Empty;
|
|
||||||
foreach (var c in Path.GetInvalidFileNameChars()) {
|
|
||||||
s = s.Replace(c, '-');
|
|
||||||
}
|
|
||||||
s = s.Replace('_', '-');
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _GenerateFieldsFromFilename(string filename) {
|
|
||||||
var split = filename.Split('_');
|
|
||||||
Prefix = split[0];
|
|
||||||
if (split.Length == 3) {
|
|
||||||
IdentificationString = (null, split[1]);
|
|
||||||
} else if (split.Length == 4) {
|
|
||||||
IdentificationString = (split[1], split[2]);
|
|
||||||
} else {
|
|
||||||
IdentificationString = (null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string? GetLog() {
|
|
||||||
if (_log == null) return null;
|
|
||||||
return _log.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Log(string msg) {
|
|
||||||
if (_log == null) _log = new StringBuilder();
|
|
||||||
var prefix = DateTime.Now.ToShortTimeString() + " ";
|
|
||||||
if (File != null) prefix += File.Name + ": ";
|
|
||||||
_log.AppendLine(prefix + msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ResetLog() {
|
|
||||||
if (_log != null) _log.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call on UnUse to prevent memory hogging
|
|
||||||
public void UnUse() {
|
|
||||||
_Element = null;
|
|
||||||
_log = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public XElement GetElement() {
|
|
||||||
if (_Element == null)
|
|
||||||
_Element = _GetElement();
|
|
||||||
return _Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
private XElement _GetElement() {
|
|
||||||
if (File == null || String.IsNullOrWhiteSpace(File.PhysicalPath) || !File.Exists)
|
|
||||||
throw new Exception("Es ist kein Pfad für die XML-Datei vorhanden.");
|
|
||||||
|
|
||||||
if (XMLRoot == null)
|
|
||||||
throw new Exception("Kein gültiges Hamann-Dokument: " + File.PhysicalPath + "Vom Prefix: " + Prefix);
|
|
||||||
|
|
||||||
XDocument? doc = null;
|
|
||||||
try {
|
|
||||||
doc = XDocument.Load(File.PhysicalPath, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
throw new Exception("Fehler beim Lesen des Dokuments: " + ex.Message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (doc == null || doc.Root == null)
|
|
||||||
throw new Exception("Das Dokument ist ungültig und kann nicht gelesen werden: " + File.PhysicalPath);
|
|
||||||
|
|
||||||
var element = XMLRoot.IsTypeOf(doc.Root);
|
|
||||||
if (element == null || !element.Any())
|
|
||||||
throw new Exception("Kein gültiges Hamann-Dokument: " + File.PhysicalPath + "Vom Prefix: " + Prefix);
|
|
||||||
|
|
||||||
return element.First();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Save(Stream stream, ModelStateDictionary state) {
|
|
||||||
if (XMLRoot == null) {
|
|
||||||
state.AddModelError("Error", "No corresponding Root Element found.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_Element == null) {
|
|
||||||
if (File == null) {
|
|
||||||
state.AddModelError("Error", "There is neither a file nor a saved element for this Document aborting the save.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_Element = GetElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
await XMLRoot.CreateHamannDocument(_Element).SaveAsync(stream, SaveOptions.DisableFormatting, new CancellationToken());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
36
HaWeb/Models/XMLStateViewModel.cs
Normal file
36
HaWeb/Models/XMLStateViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
namespace HaWeb.Models;
|
||||||
|
using HaWeb.XMLParser;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
|
|
||||||
|
public class XMLStateViewModel {
|
||||||
|
// Titel der Seite / Aktiver Präfix
|
||||||
|
public string ActiveTitle { get; private set; }
|
||||||
|
public IFileInfo? ActiveFile { get; set; }
|
||||||
|
public (DateTime PullTime, string Hash)? GitData { get; private set; }
|
||||||
|
public bool ValidState { get; private set; }
|
||||||
|
|
||||||
|
// Verfügbare Datei-Typen
|
||||||
|
public List<IXMLRoot>? AvailableRoots { get; private set; }
|
||||||
|
|
||||||
|
// Akuell geladene Dateien
|
||||||
|
public List<FileModel>? ManagedFiles { get; private set; }
|
||||||
|
|
||||||
|
// Verfügbare (Gesamt-)Dateien
|
||||||
|
public List<IFileInfo>? HamannFiles { get; set; }
|
||||||
|
|
||||||
|
public XMLStateViewModel(
|
||||||
|
string title,
|
||||||
|
(DateTime PullTime, string Hash)? gitData,
|
||||||
|
List<IXMLRoot>? roots,
|
||||||
|
List<IFileInfo>? hamannFiles,
|
||||||
|
List<FileModel>? managedFiles,
|
||||||
|
bool validState) {
|
||||||
|
ActiveTitle = title;
|
||||||
|
AvailableRoots = roots;
|
||||||
|
HamannFiles = hamannFiles;
|
||||||
|
ManagedFiles = managedFiles;
|
||||||
|
GitData = gitData;
|
||||||
|
ValidState = validState;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,42 +4,64 @@ using HaDocument.Interfaces;
|
|||||||
using HaWeb.XMLParser;
|
using HaWeb.XMLParser;
|
||||||
using HaWeb.XMLTests;
|
using HaWeb.XMLTests;
|
||||||
using HaWeb.FileHelpers;
|
using HaWeb.FileHelpers;
|
||||||
|
using HaWeb.BackgroundTask;
|
||||||
using Microsoft.FeatureManagement;
|
using Microsoft.FeatureManagement;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
List<string> configpaths = new List<string>();
|
||||||
|
|
||||||
|
// Add additional configuration
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
var p = builder.Configuration.GetValue<string>("WorkingTreePathWindows") + "settings.json";
|
||||||
|
configpaths.Add(p);
|
||||||
|
builder.Configuration.AddJsonFile(p, optional: true, reloadOnChange: true);
|
||||||
|
} else {
|
||||||
|
var p = builder.Configuration.GetValue<string>("WorkingTreePathLinux") + "settings.json";
|
||||||
|
configpaths.Add(p);
|
||||||
|
builder.Configuration.AddJsonFile(p, optional: true, reloadOnChange: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Create initial Data
|
||||||
|
var tS = new XMLTestService();
|
||||||
|
var XMLIS = new XMLInteractionService(builder.Configuration, tS);
|
||||||
|
var hdW = new HaDocumentWrapper(XMLIS, builder.Configuration);
|
||||||
|
var XMLFP = new XMLFileProvider(XMLIS, hdW, builder.Configuration);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddControllersWithViews();
|
builder.Services.AddControllersWithViews();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
|
|
||||||
// // To get files from a path provided by configuration:
|
|
||||||
// TODO: Test Read / Write Access
|
|
||||||
string? filepath = null;
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
|
||||||
filepath = builder.Configuration.GetValue<string>("StoredFilePathWindows");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
filepath = builder.Configuration.GetValue<string>("StoredFilePathLinux");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filepath == null) {
|
|
||||||
throw new Exception("You need to set a specific Filepath, either StoredFilePathWindows or StoredFilePathLinux");
|
|
||||||
}
|
|
||||||
|
|
||||||
var physicalProvider = new PhysicalFileProvider(filepath);
|
|
||||||
|
|
||||||
builder.Services.AddSingleton<IFileProvider>(physicalProvider);
|
|
||||||
builder.Services.AddTransient<IReaderService, ReaderService>();
|
builder.Services.AddTransient<IReaderService, ReaderService>();
|
||||||
builder.Services.AddSingleton<IXMLProvider, XMLProvider>();
|
builder.Services.AddSingleton<IXMLTestService, XMLTestService>((_) => tS);
|
||||||
builder.Services.AddSingleton<IXMLService, XMLService>();
|
builder.Services.AddSingleton<IXMLInteractionService, XMLInteractionService>((_) => XMLIS);
|
||||||
builder.Services.AddSingleton<HaWeb.FileHelpers.IHaDocumentWrappper, HaWeb.FileHelpers.HaDocumentWrapper>();
|
builder.Services.AddSingleton<IHaDocumentWrappper, HaDocumentWrapper>((_) => hdW);
|
||||||
builder.Services.AddSingleton<IXMLTestService, XMLTestService>();
|
builder.Services.AddSingleton<IXMLFileProvider, XMLFileProvider>(_ => XMLFP);
|
||||||
|
// builder.Services.AddSingleton<IConfigurationMonitor, ConfigurationMonitor>();
|
||||||
|
// builder.Services.AddHostedService<QueuedHostedService>();
|
||||||
|
// builder.Services.AddSingleton<IBackgroundTaskQueue>(ctx =>
|
||||||
|
// {
|
||||||
|
// if (!int.TryParse(builder.Configuration["QueueCapacity"], out var queueCapacity))
|
||||||
|
// queueCapacity = 100;
|
||||||
|
// return new BackgroundTaskQueue(queueCapacity);
|
||||||
|
// });
|
||||||
|
// builder.Services.AddSingleton<IMonitorLoop, MonitorLoop>();
|
||||||
builder.Services.AddFeatureManagement();
|
builder.Services.AddFeatureManagement();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Reload config on change
|
||||||
|
var cM = new ConfigurationMonitor(configpaths.ToArray(), app.Services);
|
||||||
|
ChangeToken.OnChange(
|
||||||
|
() => app.Configuration.GetReloadToken(),
|
||||||
|
(state) => cM.InvokeChanged(state),
|
||||||
|
configpaths.ToArray()
|
||||||
|
);
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@@ -47,6 +69,7 @@ if (!app.Environment.IsDevelopment())
|
|||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
app.UseForwardedHeaders(new ForwardedHeadersOptions{ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class SearchRules {
|
|||||||
if (sb.Length >= sw.Length) {
|
if (sb.Length >= sw.Length) {
|
||||||
if (sb.ToString().Contains(sw)) {
|
if (sb.ToString().Contains(sw)) {
|
||||||
if (reader.State.Results == null)
|
if (reader.State.Results == null)
|
||||||
reader.State.Results = new List<(string Page, string Line, string Identifier)>();
|
reader.State.Results = new List<(string Page, string Line, string? Identifier)>();
|
||||||
reader.State.Results.Add((reader.CurrentPage, reader.CurrentLine, reader.State.CurrentIdentifier));
|
reader.State.Results.Add((reader.CurrentPage, reader.CurrentLine, reader.State.CurrentIdentifier));
|
||||||
}
|
}
|
||||||
sb.Remove(0, sb.Length - sw.Length);
|
sb.Remove(0, sb.Length - sw.Length);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public class SearchState : HaWeb.HTMLParser.IState {
|
|||||||
internal string? CurrentIdentifier;
|
internal string? CurrentIdentifier;
|
||||||
internal ILibrary? Lib;
|
internal ILibrary? Lib;
|
||||||
internal bool Normalize;
|
internal bool Normalize;
|
||||||
internal List<(string Page, string Line, string Identifier)>? Results;
|
internal List<(string Page, string Line, string? Identifier)>? Results;
|
||||||
|
|
||||||
public SearchState(string searchword, bool normalize = false, ILibrary? lib = null) {
|
public SearchState(string searchword, bool normalize = false, ILibrary? lib = null) {
|
||||||
Lib = lib;
|
Lib = lib;
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ using HaWeb.XMLTests;
|
|||||||
public class AppNode : INodeRule
|
public class AppNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "app";
|
public string Name => "app";
|
||||||
public string XPath => "//app";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "ueberlieferung" },
|
||||||
|
XPath = "//app"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//appDef", "index")
|
("ref", new HamannXPath() { Documents = new[] { "personenorte" }, XPath = "//appDef" }, "index")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class AutopsicNode : INodeRule
|
public class AutopsicNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "autopsic";
|
public string Name => "autopsic";
|
||||||
public string XPath => "//autopsic";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "metadaten" },
|
||||||
|
XPath = "//autopsic"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "value" };
|
public string[]? Attributes { get; } = { "value" };
|
||||||
public string? uniquenessAttribute => "value" ;
|
public string? uniquenessAttribute => "value" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,14 @@ using HaWeb.XMLTests;
|
|||||||
public class EditNode : INodeRule
|
public class EditNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "edit";
|
public string Name => "edit";
|
||||||
public string XPath => "//edit";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "brieftext", "texteingriffe", "ueberlieferung" },
|
||||||
|
XPath = "//edit"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//editreason", "index")
|
("ref", new HamannXPath() { Documents = new[] { "texteingriffe" }, XPath = "//editreason" }, "index")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,14 @@ using HaWeb.XMLTests;
|
|||||||
public class HandNode : INodeRule
|
public class HandNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "hand";
|
public string Name => "hand";
|
||||||
public string XPath => "//hand";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "ueberlieferung", "brieftext", "texteingriffe" },
|
||||||
|
XPath = "//hand"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//handDef", "index")
|
("ref", new HamannXPath() { Documents = new[] { "personenorte" }, XPath = "//handDef" }, "index")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class KommentarNode : INodeRule
|
public class KommentarNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "kommentar";
|
public string Name => "kommentar";
|
||||||
public string XPath => "//kommentar";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "register" },
|
||||||
|
XPath = "//kommentar"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "id" };
|
public string[]? Attributes { get; } = { "id" };
|
||||||
public string? uniquenessAttribute => "id" ;
|
public string? uniquenessAttribute => "id" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class LetterDescNode : INodeRule
|
public class LetterDescNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "letterDesc";
|
public string Name => "letterDesc";
|
||||||
public string XPath => "//letterDesc";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "metadaten" },
|
||||||
|
XPath = "//letterDesc"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => "ref" ;
|
public string? uniquenessAttribute => "ref" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class LetterTextNode : INodeRule
|
public class LetterTextNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "letterText";
|
public string Name => "letterText";
|
||||||
public string XPath => "//letterText";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "brieftext" },
|
||||||
|
XPath = "//letterText"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "index" };
|
public string[]? Attributes { get; } = { "index" };
|
||||||
public string? uniquenessAttribute => "index" ;
|
public string? uniquenessAttribute => "index" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class LetterTraditionNode : INodeRule
|
public class LetterTraditionNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "letterTradition";
|
public string Name => "letterTradition";
|
||||||
public string XPath => "//letterTradition";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "ueberlieferung" },
|
||||||
|
XPath = "//letterTradition"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => "ref" ;
|
public string? uniquenessAttribute => "ref" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,15 @@ using HaWeb.XMLTests;
|
|||||||
public class LinkNode : INodeRule
|
public class LinkNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "link";
|
public string Name => "link";
|
||||||
public string XPath => "//link";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "ueberlieferung", "stellenkommentar", "register", "texteingriffe" },
|
||||||
|
XPath = "//link"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = null;
|
public string[]? Attributes { get; } = null;
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//kommentar", "id"),
|
("ref", new HamannXPath() { Documents = new[] { "register" }, XPath = "//kommentar" }, "id"),
|
||||||
("subref", "//subsection", "id")
|
("subref", new HamannXPath() { Documents = new[] { "register" }, XPath = "//subsection" }, "id"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class MarginalNode : INodeRule
|
public class MarginalNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "marginal";
|
public string Name => "marginal";
|
||||||
public string XPath => "//marginal";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "stellenkommentar" },
|
||||||
|
XPath = "//marginal"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "index", "letter", "page", "line" };
|
public string[]? Attributes { get; } = { "index", "letter", "page", "line" };
|
||||||
public string? uniquenessAttribute => "index";
|
public string? uniquenessAttribute => "index";
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,14 @@ using HaWeb.XMLTests;
|
|||||||
public class Receiver : INodeRule
|
public class Receiver : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "receiver";
|
public string Name => "receiver";
|
||||||
public string XPath => "//receiver";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "metadaten" },
|
||||||
|
XPath = "//receiver"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//personDef", "index")
|
("ref", new HamannXPath() { Documents = new[] { "personenorte" }, XPath = "//personDef" }, "index")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,14 @@ using HaWeb.XMLTests;
|
|||||||
public class SenderNode : INodeRule
|
public class SenderNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "sender";
|
public string Name => "sender";
|
||||||
public string XPath => "//sender";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "metadaten" },
|
||||||
|
XPath = "//sender"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "ref" };
|
public string[]? Attributes { get; } = { "ref" };
|
||||||
public string? uniquenessAttribute => null;
|
public string? uniquenessAttribute => null;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
("ref", "//personDef", "index")
|
("ref", new HamannXPath() { Documents = new[] { "personenorte" }, XPath = "//personDef" }, "index")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -5,8 +5,15 @@ namespace HaWeb.Settings.NodeRules;
|
|||||||
|
|
||||||
public class StructureCollection : ICollectionRule {
|
public class StructureCollection : ICollectionRule {
|
||||||
public string Name { get; } = "structure";
|
public string Name { get; } = "structure";
|
||||||
public string[] Bases { get; } = { "//letterText", "//letterTradition" };
|
public HamannXPath[] Bases { get; } = {
|
||||||
public string[] Backlinks { get; } = { "//intlink", "//marginal" };
|
new HamannXPath() { Documents = new[] { "brieftext" }, XPath = "//letterText" },
|
||||||
|
new HamannXPath() { Documents = new[] { "ueberlieferung" }, XPath = "//letterTradition"}
|
||||||
|
};
|
||||||
|
public HamannXPath[] Backlinks { get; } = {
|
||||||
|
new HamannXPath() { Documents = new[] { "stellenkommentar", "ueberlieferung", "texteingriffe", "register" }, XPath = "//intlink" },
|
||||||
|
new HamannXPath() { Documents = new[] { "stellenkommentar" }, XPath = "//marginal"}
|
||||||
|
};
|
||||||
|
|
||||||
public IEnumerable<(string, XElement, XMLRootDocument)> GenerateIdentificationStrings(IEnumerable<(XElement, XMLRootDocument)> list) {
|
public IEnumerable<(string, XElement, XMLRootDocument)> GenerateIdentificationStrings(IEnumerable<(XElement, XMLRootDocument)> list) {
|
||||||
foreach (var e in list) {
|
foreach (var e in list) {
|
||||||
var id = e.Item1.Name == "letterText" ? e.Item1.Attribute("index")!.Value : e.Item1.Attribute("ref")!.Value;
|
var id = e.Item1.Name == "letterText" ? e.Item1.Attribute("index")!.Value : e.Item1.Attribute("ref")!.Value;
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ using HaWeb.XMLTests;
|
|||||||
public class SubsectionNode : INodeRule
|
public class SubsectionNode : INodeRule
|
||||||
{
|
{
|
||||||
public string Name => "subsection";
|
public string Name => "subsection";
|
||||||
public string XPath => "//subsection";
|
public HamannXPath XPath => new HamannXPath() {
|
||||||
|
Documents = new[] { "register" },
|
||||||
|
XPath = "//subsection"
|
||||||
|
};
|
||||||
public string[]? Attributes { get; } = { "id" };
|
public string[]? Attributes { get; } = { "id" };
|
||||||
public string? uniquenessAttribute => "id" ;
|
public string? uniquenessAttribute => "id" ;
|
||||||
public List<(string, string, string)>? References { get; } = new List<(string, string, string)>()
|
public List<(string, HamannXPath, string)>? References { get; } = new List<(string, HamannXPath, string)>()
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class CommentRoot : HaWeb.XMLParser.IXMLRoot {
|
public class CommentRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Register";
|
public string Type { get; } = "Register";
|
||||||
public string Prefix { get; } = "register";
|
public string Prefix { get; } = "register";
|
||||||
public string[] XPathContainer { get; } = { ".//data//kommentare/kommcat", ".//kommentare/kommcat" };
|
public string[] XPathContainer { get; } = { "/opus/data//kommentare/kommcat", "/opus//kommentare/kommcat" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "kommentar") return true;
|
if (elem.Name == "kommentar") return true;
|
||||||
@@ -39,7 +39,7 @@ public class CommentRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("kommentare") == null)
|
if (file.Element("kommentare") == null)
|
||||||
file.AddFirst(new XElement("kommentare"));
|
file.AddFirst(new XElement("kommentare"));
|
||||||
file.Element("kommentare")!.AddFirst(document.GetElement());
|
file.Element("kommentare")!.AddFirst(document.Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot {
|
public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Metadaten";
|
public string Type { get; } = "Metadaten";
|
||||||
public string Prefix { get; } = "metadaten";
|
public string Prefix { get; } = "metadaten";
|
||||||
public string[] XPathContainer { get; } = { ".//data/descriptions", ".//descriptions" };
|
public string[] XPathContainer { get; } = { "/opus/data/descriptions", "/opus/descriptions" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "letterDesc") return true;
|
if (elem.Name == "letterDesc") return true;
|
||||||
@@ -34,7 +34,7 @@ public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("descriptions") == null)
|
if (file.Element("descriptions") == null)
|
||||||
file.AddFirst(new XElement("descriptions"));
|
file.AddFirst(new XElement("descriptions"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("descriptions");
|
var root = file.Element("descriptions");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using System.IO;
|
|||||||
public class DocumentRoot : HaWeb.XMLParser.IXMLRoot {
|
public class DocumentRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Brieftext";
|
public string Type { get; } = "Brieftext";
|
||||||
public string Prefix { get; } = "brieftext";
|
public string Prefix { get; } = "brieftext";
|
||||||
public string[] XPathContainer { get; } = { ".//data/document", ".//document" };
|
public string[] XPathContainer { get; } = { "/opus/data/document", "/opus/document" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "letterText") return true;
|
if (elem.Name == "letterText") return true;
|
||||||
@@ -35,7 +35,7 @@ public class DocumentRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("document") == null)
|
if (file.Element("document") == null)
|
||||||
file.AddFirst(new XElement("document"));
|
file.AddFirst(new XElement("document"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("document");
|
var root = file.Element("document");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class EditsRoot : HaWeb.XMLParser.IXMLRoot {
|
public class EditsRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Texteingriffe";
|
public string Type { get; } = "Texteingriffe";
|
||||||
public string Prefix { get; } = "texteingriffe";
|
public string Prefix { get; } = "texteingriffe";
|
||||||
public string[] XPathContainer { get; } = { ".//data/edits", ".//edits" };
|
public string[] XPathContainer { get; } = { "/opus/data/edits", "/opus/edits" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "editreason") return true;
|
if (elem.Name == "editreason") return true;
|
||||||
@@ -34,7 +34,7 @@ public class EditsRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("edits") == null)
|
if (file.Element("edits") == null)
|
||||||
file.AddFirst(new XElement("edits"));
|
file.AddFirst(new XElement("edits"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("edits");
|
var root = file.Element("edits");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot {
|
public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Stellenkommentar";
|
public string Type { get; } = "Stellenkommentar";
|
||||||
public string Prefix { get; } = "stellenkommentar";
|
public string Prefix { get; } = "stellenkommentar";
|
||||||
public string[] XPathContainer { get; } = { ".//data/marginalien", ".//marginalien" };
|
public string[] XPathContainer { get; } = { "/opus/data/marginalien", "/opus/marginalien" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "marginal") return true;
|
if (elem.Name == "marginal") return true;
|
||||||
@@ -34,7 +34,7 @@ public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("marginalien") == null)
|
if (file.Element("marginalien") == null)
|
||||||
file.AddFirst(new XElement("marginalien"));
|
file.AddFirst(new XElement("marginalien"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("marginalien");
|
var root = file.Element("marginalien");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot {
|
public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Personen / Orte";
|
public string Type { get; } = "Personen / Orte";
|
||||||
public string Prefix { get; } = "personenorte";
|
public string Prefix { get; } = "personenorte";
|
||||||
public string[] XPathContainer { get; } = { ".//data/definitions", ".//definitions" };
|
public string[] XPathContainer { get; } = { "/opus/data/definitions", "/opus/definitions" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "personDefs" || elem.Name == "structureDefs" || elem.Name == "handDefs" || elem.Name == "locationDefs" || elem.Name == "appDefs")
|
if (elem.Name == "personDefs" || elem.Name == "structureDefs" || elem.Name == "handDefs" || elem.Name == "locationDefs" || elem.Name == "appDefs")
|
||||||
@@ -35,7 +35,7 @@ public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("definitions") == null)
|
if (file.Element("definitions") == null)
|
||||||
file.AddFirst(new XElement("definitions"));
|
file.AddFirst(new XElement("definitions"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("definitions");
|
var root = file.Element("definitions");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ using HaWeb.XMLParser;
|
|||||||
public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot {
|
public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot {
|
||||||
public string Type { get; } = "Überlieferung";
|
public string Type { get; } = "Überlieferung";
|
||||||
public string Prefix { get; } = "ueberlieferung";
|
public string Prefix { get; } = "ueberlieferung";
|
||||||
public string[] XPathContainer { get; } = { ".//data/traditions", ".//traditions" };
|
public string[] XPathContainer { get; } = { "/opus/data/traditions", "/opus/traditions" };
|
||||||
|
|
||||||
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
public Predicate<XElement> IsCollectedObject { get; } = (elem) => {
|
||||||
if (elem.Name == "letterTradition") return true;
|
if (elem.Name == "letterTradition") return true;
|
||||||
@@ -34,7 +34,7 @@ public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot {
|
|||||||
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
public void MergeIntoFile(XElement file, XMLRootDocument document) {
|
||||||
if (file.Element("traditions") == null)
|
if (file.Element("traditions") == null)
|
||||||
file.AddFirst(new XElement("traditions"));
|
file.AddFirst(new XElement("traditions"));
|
||||||
var elements = document.GetElement().Elements().Where(x => IsCollectedObject(x));
|
var elements = document.Element.Elements().Where(x => IsCollectedObject(x));
|
||||||
var root = file.Element("traditions");
|
var root = file.Element("traditions");
|
||||||
foreach (var element in elements) {
|
foreach (var element in elements) {
|
||||||
root!.Add(element);
|
root!.Add(element);
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
@model UploadViewModel;
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Upload & Veröffentlichen";
|
|
||||||
ViewData["SEODescription"] = "Johann Georg Hamann: Kommentierte Briefausgabe, Hg. v. Leonard Keidel und Janina Reibold. Durchsuchbare Online-Ausgabe der Briefe von und an Johann Georg Hamann.";
|
|
||||||
ViewData["showCredits"] = "false";
|
|
||||||
}
|
|
||||||
|
|
||||||
<script defer src="/js/upload.js" asp-append-version="true"></script>
|
|
||||||
|
|
||||||
<div class="ha-adminuploadfields" id="ha-adminuploadfields">
|
|
||||||
@foreach (var item in Model.AvailableRoots!.OrderBy(x => x.Type)) {
|
|
||||||
<a class="ha-uploadfield" asp-controller="Upload" asp-action="Index" asp-route-id="@item.Prefix">
|
|
||||||
<div class="ha-uploadfieldname">@item.Type</div>
|
|
||||||
@if (Model.UsedFiles != null && Model.UsedFiles.ContainsKey(item.Prefix)) {
|
|
||||||
<div class="ha-uploadusedfiles">
|
|
||||||
@foreach(var file in Model.UsedFiles[item.Prefix]!) {
|
|
||||||
@if (file == Model.UsedFiles[item.Prefix]!.Last())
|
|
||||||
{
|
|
||||||
<span class="ha-uploadusedfile">@file.FileName</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="ha-uploadusedfile">@file.FileName;</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
<div class="ha-uploadusedfiles ha-uploadusedfilesnotfound">Keine Datei geladen!</div>
|
|
||||||
}
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="ha-uploadpublishforms">
|
|
||||||
@await Html.PartialAsync("/Views/Shared/_UploadForm.cshtml", Model)
|
|
||||||
|
|
||||||
<a class="ha-publishbutton" asp-controller="Upload" asp-action="Index" asp-route-id="@string.Empty">
|
|
||||||
<div class="ha-publishtext">Veröffentlichen</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ha-uploadheader">
|
|
||||||
<h1 class="ha-uploadtitle">@Model.ActiveTitle</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ha-uploadcontainer">
|
|
||||||
@* File Category Page File List *@
|
|
||||||
@if (Model.AvailableFiles != null && Model.AvailableFiles.Any()) {
|
|
||||||
<div class="ha-filesheader">
|
|
||||||
<div class="ha-availablefiles" id="ha-availablefiles">
|
|
||||||
<div class="ha-availablefilestitle">Datei(en)</div>
|
|
||||||
@if(Model.UsedFiles != null && Model.UsedFiles.ContainsKey(Model.Prefix)) {
|
|
||||||
<div class="ha-usedfilelist">
|
|
||||||
@foreach (var item in Model.UsedFiles[Model.Prefix]!)
|
|
||||||
{
|
|
||||||
if(item == Model.UsedFiles[Model.Prefix]!.Last()) {
|
|
||||||
<span class="ha-usedfile">@item.FileName</span>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
<span class="ha-usedfile">@item.FileName,</span>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="ha-availablefileslist hidden" id="ha-availablefileslist">
|
|
||||||
@await Html.PartialAsync("/Views/Shared/_FileListForm.cshtml", (Model.AvailableFiles, "Verfügbare Dateien", "API", "SetUsed", Model.Prefix, "/Download/XML/" + Model.Prefix + "/", true))
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (Model.UsedFiles != null && Model.Prefix != null && Model.UsedFiles.ContainsKey(Model.Prefix)) {
|
|
||||||
<textarea class="py-2 px-3 mx-8 mb-8 shadow-lg font-mono text-sm border rounded" id="errormessagebox" name="errormessagebox" rows="25" cols="90" readonly>
|
|
||||||
@foreach (var f in Model.UsedFiles[Model.Prefix])
|
|
||||||
{
|
|
||||||
@f.Messages
|
|
||||||
}
|
|
||||||
</textarea>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@* Start Page File List *@
|
|
||||||
else {
|
|
||||||
<div class="ha-publishfilelist">
|
|
||||||
@await Html.PartialAsync("/Views/Shared/_PublishForm.cshtml", Model)
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ha-hamannfilechooser">
|
|
||||||
@await Html.PartialAsync("/Views/Shared/_FileListForm.cshtml", (Model.HamannFiles, "Verfügbare Hamann-Dateien", "API", "SetInProduction", string.Empty, "/Download/XML/", false))
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="ha-setendyearform" id="ha-setendyearform" enctype="application/x-www-form-urlencoded" asp-controller="API" asp-action="SetEndYear" onsubmit="YEARSUBMIT(this);return false;">
|
|
||||||
Verfügbare Jahre: bis
|
|
||||||
<select name="EndYear" id="">
|
|
||||||
@foreach (var y in Model.AvailableYears) {
|
|
||||||
<option>@y</option>
|
|
||||||
}
|
|
||||||
</select>
|
|
||||||
<button id="ha-setendyearbutton" class="ha-setendyearbutton" type="submit">Setzen</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
83
HaWeb/Views/Admin/Dynamic/XMLState.cshtml
Normal file
83
HaWeb/Views/Admin/Dynamic/XMLState.cshtml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
@model XMLStateViewModel;
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Syntax-Check & Dateien";
|
||||||
|
ViewData["SEODescription"] = "Johann Georg Hamann: Kommentierte Briefausgabe, Hg. v. Leonard Keidel und Janina Reibold. Durchsuchbare Online-Ausgabe der Briefe von und an Johann Georg Hamann.";
|
||||||
|
ViewData["showCredits"] = "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="ha-adminuploadfields" id="ha-adminuploadfields">
|
||||||
|
<div class="ha-uploadcontainer">
|
||||||
|
|
||||||
|
@* File Category Page File List *@
|
||||||
|
@*
|
||||||
|
<textarea class="py-2 px-3 mx-8 mb-8 shadow-lg font-mono text-sm border rounded" id="errormessagebox" name="errormessagebox" rows="25" cols="90" readonly>
|
||||||
|
@foreach (var f in Model.Files[Model.Prefix])
|
||||||
|
{
|
||||||
|
@f.Messages
|
||||||
|
}
|
||||||
|
</textarea> *@
|
||||||
|
@if (Model.ManagedFiles != null && Model.ManagedFiles.Any()) {
|
||||||
|
<div class="">
|
||||||
|
<table>
|
||||||
|
@foreach (var f in Model.ManagedFiles) {
|
||||||
|
<tr>
|
||||||
|
<td>@f.FileName</td>
|
||||||
|
<td>@f.GetLastModified()</td>
|
||||||
|
@if (f.IsValid) {
|
||||||
|
<td>Valid! @f.GetLog()</td>
|
||||||
|
} else {
|
||||||
|
<td>@f.GetLog()</td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<div class="">
|
||||||
|
Keine Dateien im Repository gefunden!
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
<script defer src="/js/filelistform.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
|
<fieldset class="ha-filelistfieldset">
|
||||||
|
<legend class="ha-filelistlegend">Verfügbare Dateien</legend>
|
||||||
|
@if(Model.HamannFiles != null && Model.HamannFiles.Any()) {
|
||||||
|
<form class="ha-selectfilesform" id="selecthamannfilesform" asp-controller="API" asp-action="SetInProduction" method="post" onsubmit="USESubmit(this);return false;" enctype="multipart/form-data">
|
||||||
|
<div class="ha-filelistlist">
|
||||||
|
@foreach (var file in Model.HamannFiles) {
|
||||||
|
<div class="ha-filelistfile">
|
||||||
|
@if (Model.ActiveFile != null) {
|
||||||
|
<input type="radio" id="@file.Name" name="file" value="@file.Name" @(file.Name == @Model.ActiveFile!.Name ? "checked='checked'" : "")>
|
||||||
|
} else {
|
||||||
|
<input type="radio" id="@file.Name" name="file" value="@file.Name">
|
||||||
|
}
|
||||||
|
<div class="ha-filelistname">@file.Name</div>
|
||||||
|
@if (Model.ActiveFile != null && file.Name == Model.ActiveFile!.Name) {
|
||||||
|
<div class="ha-filelistusedproduction">
|
||||||
|
<div class="ha-filelistproduction">in Verwendung</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@* // TODO Metadata
|
||||||
|
// TODO DELETE *@
|
||||||
|
<div class="ha-filelistmodified">@file.LastModified.LocalDateTime</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
@if (!Model.ValidState) {
|
||||||
|
<div>
|
||||||
|
Status nicht validiert! Daten können nicht auf der Webseite angezeigt werden!
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<output id ="ha-filelistoutput"></output>
|
||||||
|
<button type="submit" class="ha-filelistbutton" id="ha-filelistbutton" >
|
||||||
|
Laden
|
||||||
|
<div class="ha-lds-ellipsis-load" id="ha-lds-ellipsis-load"></div>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
<div>Keine Dateien gefunden! Es wird eine fallback-Datei verwendet!</div>
|
||||||
|
}
|
||||||
|
</fieldset>
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
@model (List<FileModel>? files, string title, string aspcontrolller, string aspaction, string id, string downloadprefix, bool multipleallowed);
|
|
||||||
|
|
||||||
|
|
||||||
<script defer src="/js/filelistform.js" asp-append-version="true"></script>
|
|
||||||
|
|
||||||
<fieldset class="ha-filelistfieldset">
|
|
||||||
<legend class="ha-filelistlegend">@Model.title</legend>
|
|
||||||
@if(Model.files != null && Model.files.Any()) {
|
|
||||||
<form class="ha-selectfilesform" id="selecthamannfilesform" asp-controller="@Model.aspcontrolller" asp-action="@Model.aspaction" asp-route-id="@Model.id" method="post" onsubmit="USESubmit(this);return false;" enctype="multipart/form-data">
|
|
||||||
<div class="ha-filelistlist">
|
|
||||||
@foreach (var file in Model.files.OrderByDescending(x => x.LastModified)) {
|
|
||||||
<div class="ha-filelistfile">
|
|
||||||
@if (Model.multipleallowed) {
|
|
||||||
<input type="checkbox" id="@file.FileName" name="file" value="@file.FileName" @(file.IsUsed ? "checked='checked'" : "")>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
<input type="radio" id="@file.FileName" name="file" value="@file.FileName" @(file.InProduction ? "checked='checked'" : "")>
|
|
||||||
}
|
|
||||||
<div class="ha-filelistname">@file.FileName</div>
|
|
||||||
@if (file.InProduction || file.IsUsed) {
|
|
||||||
<div class="ha-filelistusedproduction">
|
|
||||||
@if (file.InProduction) {
|
|
||||||
<div class="ha-filelistproduction">in Verwendung</div>
|
|
||||||
}
|
|
||||||
@if (file.IsUsed) {
|
|
||||||
<div class="ha-filelistused">geladen</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
@if (file.Fields != null && file.Fields.Any()) {
|
|
||||||
<div class="ha-filelistfields">
|
|
||||||
@foreach (var field in file.Fields) {
|
|
||||||
@if (field.Item2 != null) {
|
|
||||||
<div class="ha-filelistfield">field.Item2</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<div class="ha-filelistmodified">@file.LastModified - @file.Prefix</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<output id ="ha-filelistoutput"></output>
|
|
||||||
<button type="submit" class="ha-filelistbutton" id="ha-filelistbutton" >
|
|
||||||
Laden
|
|
||||||
<div class="ha-lds-ellipsis-load" id="ha-lds-ellipsis-load"></div>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
<div>Keine Dateien gefunden! Es wird eine fallback-Datei verwendet!</div>
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
@model UploadViewModel;
|
@model XMLStateViewModel;
|
||||||
<script defer src="/js/publishform.js" asp-append-version="true"></script>
|
<script defer src="/js/publishform.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
@if (Model.UsedFiles != null && Model.UsedFiles.Any()) {
|
@* @if (Model. != null && Model..Any()) {
|
||||||
<div class="ha-publishfilelisttitle">Aktuell geladene Dateien</div>
|
<div class="ha-publishfilelisttitle">Aktuell geladene Dateien</div>
|
||||||
<table class="ha-publishfilelistlist">
|
<table class="ha-publishfilelistlist">
|
||||||
@foreach (var (category, files) in Model.UsedFiles.OrderBy(x => x.Key))
|
@foreach (var (category, files) in Model..OrderBy(x => x.Key))
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@Model.AvailableRoots.Where(x => x.Prefix == category).First().Type:</td>
|
<td>@Model.AvailableRoots.Where(x => x.Prefix == category).First().Type:</td>
|
||||||
@@ -32,4 +32,4 @@
|
|||||||
<output form="uploadForm" name="publish-result" id="publish-result"></output>
|
<output form="uploadForm" name="publish-result" id="publish-result"></output>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
}
|
} *@
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
@model UploadViewModel;
|
@model XMLStateViewModel;
|
||||||
|
|
||||||
<script defer src="/js/uploadform.js" asp-append-version="true"></script>
|
<script defer src="/js/uploadform.js" asp-append-version="true"></script>
|
||||||
|
|
||||||
|
|||||||
@@ -5,21 +5,17 @@ using HaWeb.Models;
|
|||||||
using HaDocument.Interfaces;
|
using HaDocument.Interfaces;
|
||||||
using HaDocument.Models;
|
using HaDocument.Models;
|
||||||
using HaXMLReader.Interfaces;
|
using HaXMLReader.Interfaces;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
public interface IXMLService {
|
public interface IXMLInteractionService {
|
||||||
|
public XElement? TryCreate();
|
||||||
|
public bool GetValidState();
|
||||||
|
public void Collect(List<IFileInfo> Files);
|
||||||
|
public Dictionary<string, FileList?>? GetLoaded();
|
||||||
public IXMLRoot? GetRoot(string name);
|
public IXMLRoot? GetRoot(string name);
|
||||||
public List<IXMLRoot>? GetRootsList();
|
public List<IXMLRoot>? GetRootsList();
|
||||||
public Dictionary<string, IXMLRoot>? GetRootsDictionary();
|
public void CreateSearchables(XDocument document);
|
||||||
public List<XMLRootDocument>? ProbeFile(XDocument document, ModelStateDictionary ModelState);
|
public List<FileModel>? GetManagedFiles();
|
||||||
public Dictionary<string, FileList?>? GetUsedDictionary();
|
|
||||||
public XElement? MergeUsedDocuments(ModelStateDictionary ModelState);
|
|
||||||
public void Use(XMLRootDocument doc);
|
|
||||||
public void AutoUse(string prefix);
|
|
||||||
public void AutoUse(FileList filelist);
|
|
||||||
public Dictionary<string, FileList?>? GetInProduction();
|
|
||||||
public void UnUse(string prefix);
|
|
||||||
public void SetInProduction();
|
|
||||||
public void SetInProduction(XDocument document);
|
|
||||||
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? SearchCollection(string collection, string searchword, IReaderService reader, ILibrary? lib);
|
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? SearchCollection(string collection, string searchword, IReaderService reader, ILibrary? lib);
|
||||||
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? GetPreviews(List<(string, List<Marginal>)> places, IReaderService reader, ILibrary lib);
|
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? GetPreviews(List<(string, List<Marginal>)> places, IReaderService reader, ILibrary lib);
|
||||||
}
|
}
|
||||||
@@ -58,8 +58,5 @@ public interface IXMLRoot {
|
|||||||
// });
|
// });
|
||||||
// return ret;
|
// return ret;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
public abstract XElement CreateHamannDocument(XElement element);
|
|
||||||
|
|
||||||
public abstract void MergeIntoFile(XElement file, XMLRootDocument document);
|
public abstract void MergeIntoFile(XElement file, XMLRootDocument document);
|
||||||
}
|
}
|
||||||
@@ -1,123 +1,169 @@
|
|||||||
namespace HaWeb.XMLParser;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using System.Xml.XPath;
|
using System.Xml.XPath;
|
||||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
||||||
using HaWeb.Models;
|
|
||||||
using HaWeb.SearchHelpers;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Text;
|
|
||||||
using HaXMLReader.Interfaces;
|
|
||||||
using HaDocument.Interfaces;
|
using HaDocument.Interfaces;
|
||||||
using HaDocument.Models;
|
using HaDocument.Models;
|
||||||
|
using HaWeb.FileHelpers;
|
||||||
|
using HaWeb.Models;
|
||||||
|
using HaWeb.SearchHelpers;
|
||||||
|
using HaWeb.XMLParser;
|
||||||
using HaWeb.XMLTests;
|
using HaWeb.XMLTests;
|
||||||
|
using HaXMLReader.Interfaces;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
// XMLService provides a wrapper around the loaded and used XML data
|
// Conditions for Successful create
|
||||||
public class XMLService : IXMLService {
|
// All types there
|
||||||
private Dictionary<string, FileList?>? _Used;
|
// Merging Success
|
||||||
private Dictionary<string, IXMLRoot>? _Roots;
|
// Saving Success
|
||||||
private Dictionary<string, IXMLCollection>? _Collections;
|
// Loading Success
|
||||||
|
|
||||||
private Stack<Dictionary<string, FileList?>>? _InProduction;
|
// Startup (BEFORE IXMLFileProvider, After IHaDocumentWrapper)
|
||||||
|
public class XMLInteractionService : IXMLInteractionService {
|
||||||
|
private readonly IXMLTestService _testService;
|
||||||
|
private readonly long _fileSizeLimit;
|
||||||
|
private readonly string[] _allowedExtensions = { ".xml" };
|
||||||
|
private readonly static XmlReaderSettings _xmlSettings = new XmlReaderSettings() {
|
||||||
|
CloseInput = true,
|
||||||
|
CheckCharacters = false,
|
||||||
|
ConformanceLevel = ConformanceLevel.Fragment,
|
||||||
|
IgnoreComments = true,
|
||||||
|
IgnoreProcessingInstructions = true,
|
||||||
|
IgnoreWhitespace = false
|
||||||
|
};
|
||||||
|
|
||||||
private Dictionary<string, ItemsCollection>? _collectedProduction;
|
private Dictionary<string, IXMLRoot>? _RootDefs;
|
||||||
private Dictionary<string, ItemsCollection>? _collectedUsed;
|
private Dictionary<string, IXMLCollection>? _CollectionDefs;
|
||||||
|
|
||||||
public XMLService() {
|
private List<FileModel>? _ManagedFiles;
|
||||||
// Getting all classes which implement IXMLRoot for possible document endpoints
|
private Dictionary<string, FileList?>? _Loaded;
|
||||||
|
private Dictionary<string, ItemsCollection>? _Collection;
|
||||||
|
|
||||||
|
private bool _ValidState = false;
|
||||||
|
|
||||||
|
public XMLInteractionService(IConfiguration config, IXMLTestService testService) {
|
||||||
|
_testService = testService;
|
||||||
|
_fileSizeLimit = config.GetValue<long>("FileSizeLimit");
|
||||||
var roottypes = _GetAllTypesThatImplementInterface<IXMLRoot>().ToList();
|
var roottypes = _GetAllTypesThatImplementInterface<IXMLRoot>().ToList();
|
||||||
roottypes.ForEach( x => {
|
roottypes.ForEach( x => {
|
||||||
if (this._Roots == null) this._Roots = new Dictionary<string, IXMLRoot>();
|
if (this._RootDefs == null) this._RootDefs = new Dictionary<string, IXMLRoot>();
|
||||||
var instance = (IXMLRoot)Activator.CreateInstance(x)!;
|
var instance = (IXMLRoot)Activator.CreateInstance(x)!;
|
||||||
if (instance != null) this._Roots.Add(instance.Prefix, instance);
|
if (instance != null) this._RootDefs.Add(instance.Prefix, instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
var collectiontypes = _GetAllTypesThatImplementInterface<IXMLCollection>().ToList();
|
var collectiontypes = _GetAllTypesThatImplementInterface<IXMLCollection>().ToList();
|
||||||
collectiontypes.ForEach( x => {
|
collectiontypes.ForEach( x => {
|
||||||
if (this._Collections == null) this._Collections = new Dictionary<string, IXMLCollection>();
|
if (this._CollectionDefs == null) this._CollectionDefs = new Dictionary<string, IXMLCollection>();
|
||||||
var instance = (IXMLCollection)Activator.CreateInstance(x)!;
|
var instance = (IXMLCollection)Activator.CreateInstance(x)!;
|
||||||
if (instance != null && instance.IsGlobal()) this._Collections.Add(instance.Key, instance);
|
if (instance != null && instance.IsGlobal()) this._CollectionDefs.Add(instance.Key, instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_Roots == null || !_Roots.Any())
|
if (_RootDefs == null || !_RootDefs.Any())
|
||||||
throw new Exception("No classes for upload endpoints were found!");
|
throw new Exception("No classes for upload endpoints were found!");
|
||||||
|
|
||||||
if (_Collections == null || !_Collections.Any())
|
if (_CollectionDefs == null || !_CollectionDefs.Any())
|
||||||
throw new Exception("No classes for object collection were found!");
|
throw new Exception("No classes for object collection were found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
public Dictionary<string, FileList?>? GetLoaded() => this._Loaded;
|
||||||
|
|
||||||
|
public List<FileModel>? GetManagedFiles() => this._ManagedFiles;
|
||||||
|
|
||||||
|
public List<IXMLRoot>? GetRootsList() => this._RootDefs == null ? null : this._RootDefs.Values.ToList();
|
||||||
|
|
||||||
|
public bool GetValidState() => this._ValidState;
|
||||||
|
|
||||||
public IXMLRoot? GetRoot(string name) {
|
public IXMLRoot? GetRoot(string name) {
|
||||||
if (_Roots == null) return null;
|
if (_RootDefs == null) return null;
|
||||||
_Roots.TryGetValue(name, out var root);
|
_RootDefs.TryGetValue(name, out var root);
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IXMLRoot>? GetRootsList() => this._Roots == null ? null : this._Roots.Values.ToList();
|
// Functions
|
||||||
|
public void Collect(List<IFileInfo> files) {
|
||||||
public Dictionary<string, IXMLRoot>? GetRootsDictionary() => this._Roots == null ? null : this._Roots;
|
if (files == null || !files.Any()) return;
|
||||||
|
_ValidState = true;
|
||||||
public Dictionary<string, FileList?>? GetInProduction() {
|
List<FileModel> res = new List<FileModel>();
|
||||||
if (_InProduction == null) return null;
|
foreach (var f in files) {
|
||||||
return this._InProduction.Peek();
|
var sb = new StringBuilder();
|
||||||
}
|
var m = _CreateFileModel(f, null);
|
||||||
|
res.Add(m);
|
||||||
public void SetInProduction() {
|
// 1. Open File for Reading
|
||||||
if (_Used == null) return;
|
try {
|
||||||
var inProduction = new Dictionary<string, FileList?>();
|
using (Stream file = f.CreateReadStream()) {
|
||||||
foreach (var category in _Used) {
|
// 2. Some security checks, if file empty, wrong start, wrong extension, too big
|
||||||
if (category.Value == null || category.Value.GetFileList() == null || !category.Value.GetFileList()!.Any())
|
if (!XMLFileHelpers.ProcessFile(file, f.Name, sb, _allowedExtensions, _fileSizeLimit)) {
|
||||||
return;
|
m.Log(sb.ToString());
|
||||||
inProduction.Add(category.Key, category.Value);
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
if(_InProduction == null) _InProduction = new Stack<Dictionary<string, FileList?>>();
|
|
||||||
_InProduction.Push(inProduction);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInProduction(XDocument document) {
|
|
||||||
if (document == null || _Roots == null) return;
|
|
||||||
int numProcs = Environment.ProcessorCount;
|
|
||||||
int concurrencyLevel = numProcs * 2;
|
|
||||||
int startingSize = 2909;
|
|
||||||
int startingSizeAllCollections = 23;
|
|
||||||
var ret = new ConcurrentDictionary<string, ItemsCollection>(concurrencyLevel, startingSizeAllCollections);
|
|
||||||
|
|
||||||
if (_Collections != null)
|
|
||||||
Parallel.ForEach(_Collections, (coll) => {
|
|
||||||
var elem = coll.Value.xPath.Aggregate(new List<XElement>(), (x, y) => { x.AddRange(document.XPathSelectElements(y).ToList()); return x; } );
|
|
||||||
if (elem != null && elem.Any()) {
|
|
||||||
var items = new ConcurrentDictionary<string, CollectedItem>(concurrencyLevel, startingSize);
|
|
||||||
foreach (var e in elem) {
|
|
||||||
var k = coll.Value.GenerateKey(e);
|
|
||||||
if (k != null) {
|
|
||||||
var searchtext = coll.Value.Searchable ?
|
|
||||||
StringHelpers.NormalizeWhiteSpace(e.ToString(), ' ', false) :
|
|
||||||
null;
|
|
||||||
var datafileds = coll.Value.GenerateDataFields != null ?
|
|
||||||
coll.Value.GenerateDataFields(e) :
|
|
||||||
null;
|
|
||||||
items[k] = new CollectedItem(k, e, coll.Value, datafileds, searchtext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (items.Any()) {
|
|
||||||
if (!ret.ContainsKey(coll.Key))
|
|
||||||
ret[coll.Key] = new ItemsCollection(coll.Key, coll.Value);
|
|
||||||
foreach (var item in items)
|
|
||||||
ret[coll.Key].Items.Add(item.Key, item.Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
} catch {
|
||||||
|
m.Log( "Datei konnte nicht geöffnet werden.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (ret.Any()) {
|
// 3. Check validity of XML
|
||||||
Parallel.ForEach(ret, (collection) => {
|
try {
|
||||||
collection.Value.GenerateGroupings();
|
using (var xmlreader = XmlReader.Create(f.CreateReadStream(), _xmlSettings)) {
|
||||||
});
|
var doc = XDocument.Load(xmlreader, LoadOptions.PreserveWhitespace | LoadOptions.SetLineInfo);
|
||||||
|
|
||||||
|
// 4. Check if opus-Document
|
||||||
|
// TODO: Unter der HOOD werden in ProbeFiles noch eigene Files gebaut!
|
||||||
|
var docs = _ProbeFile(doc, m);
|
||||||
|
if (docs == null || !docs.Any()) continue;
|
||||||
|
|
||||||
|
// Success! File can be recognized and parsed.
|
||||||
|
m.Validate();
|
||||||
|
foreach (var d in docs) {
|
||||||
|
if (_Loaded == null) _Loaded = new Dictionary<string, FileList?>();
|
||||||
|
if (!_Loaded.ContainsKey(d.Prefix)) _Loaded.Add(d.Prefix, new FileList(d.XMLRoot));
|
||||||
|
_Loaded[d.Prefix]!.Add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
m.Log($"Ungültiges XML: {ex.Message}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_collectedProduction = ret.ToDictionary(x => x.Key, y => y.Value);
|
if(res.Any()) this._ManagedFiles = res;
|
||||||
|
|
||||||
|
// Set validity
|
||||||
|
foreach (var f in _ManagedFiles) {
|
||||||
|
if (!f.IsValid) _ValidState = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Speed up this:
|
||||||
|
var sw = new Stopwatch();
|
||||||
|
sw.Start();
|
||||||
|
_testService.Test(this);
|
||||||
|
sw.Stop();
|
||||||
|
Console.WriteLine("Syntaxcheck " + sw.ElapsedMilliseconds.ToString() + " ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
public XElement? TryCreate() {
|
||||||
|
if (_Loaded == null || !_Loaded.Any() || _RootDefs == null || !_RootDefs.Any() || !_ValidState) return null;
|
||||||
|
var opus = new XElement("opus");
|
||||||
|
// TODO: Workaround for bug in HaDocument: roots have to be added in a specific order
|
||||||
|
var used = _Loaded.OrderByDescending(x => x.Key);
|
||||||
|
foreach (var category in used) {
|
||||||
|
if (category.Value == null || category.Value.GetFileList() == null || !category.Value.GetFileList()!.Any()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var documents = category.Value.GetFileList();
|
||||||
|
foreach (var document in documents!) {
|
||||||
|
document.XMLRoot.MergeIntoFile(opus, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opus;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? GetPreviews(List<(string, List<Marginal>)> places, IReaderService reader, ILibrary lib) {
|
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? GetPreviews(List<(string, List<Marginal>)> places, IReaderService reader, ILibrary lib) {
|
||||||
var searchableObjects = _collectedProduction["letters"].Items;
|
if (!_Collection.ContainsKey("letters")) return null;
|
||||||
|
var searchableObjects = _Collection["letters"].Items;
|
||||||
var res = new ConcurrentBag<(string Index, List<(string Page, string Line, string preview, string identifier)> Results)>();
|
var res = new ConcurrentBag<(string Index, List<(string Page, string Line, string preview, string identifier)> Results)>();
|
||||||
|
|
||||||
Parallel.ForEach(places, (obj) => {
|
Parallel.ForEach(places, (obj) => {
|
||||||
@@ -148,8 +194,8 @@ public class XMLService : IXMLService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? SearchCollection(string collection, string searchword, IReaderService reader, ILibrary lib) {
|
public List<(string Index, List<(string Page, string Line, string Preview, string Identifier)> Results)>? SearchCollection(string collection, string searchword, IReaderService reader, ILibrary lib) {
|
||||||
if (!_collectedProduction.ContainsKey(collection)) return null;
|
if (!_Collection.ContainsKey(collection)) return null;
|
||||||
var searchableObjects = _collectedProduction[collection].Items;
|
var searchableObjects = _Collection[collection].Items;
|
||||||
var res = new ConcurrentBag<(string Index, List<(string Page, string Line, string preview, string identifier)> Results)>();
|
var res = new ConcurrentBag<(string Index, List<(string Page, string Line, string preview, string identifier)> Results)>();
|
||||||
var sw = StringHelpers.NormalizeWhiteSpace(searchword.Trim());
|
var sw = StringHelpers.NormalizeWhiteSpace(searchword.Trim());
|
||||||
|
|
||||||
@@ -202,110 +248,88 @@ public class XMLService : IXMLService {
|
|||||||
return res.ToList();
|
return res.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<XMLRootDocument>? ProbeFile(XDocument document, ModelStateDictionary ModelState) {
|
public void CreateSearchables(XDocument document) {
|
||||||
if (document.Root!.Name != "opus") {
|
if (document == null || _RootDefs == null) return;
|
||||||
ModelState.AddModelError("Error", "A valid Hamann-Docuemnt must begin with <opus>");
|
int numProcs = Environment.ProcessorCount;
|
||||||
return null;
|
int concurrencyLevel = numProcs * 2;
|
||||||
}
|
int startingSize = 2909;
|
||||||
|
int startingSizeAllCollections = 23;
|
||||||
|
var ret = new ConcurrentDictionary<string, ItemsCollection>(concurrencyLevel, startingSizeAllCollections);
|
||||||
|
|
||||||
List<XMLRootDocument>? res = null;
|
if (_CollectionDefs != null)
|
||||||
if (document.Root != null && _Roots != null) {
|
Parallel.ForEach(_CollectionDefs, (coll) => {
|
||||||
foreach (var (_, root) in _Roots) {
|
var elem = coll.Value.xPath.Aggregate(new List<XElement>(), (x, y) => { x.AddRange(document.XPathSelectElements(y).ToList()); return x; } );
|
||||||
var elements = root.IsTypeOf(document.Root);
|
if (elem != null && elem.Any()) {
|
||||||
if (elements != null && elements.Any())
|
var items = new ConcurrentDictionary<string, CollectedItem>(concurrencyLevel, startingSize);
|
||||||
foreach (var elem in elements) {
|
foreach (var e in elem) {
|
||||||
if (res == null) res = new List<XMLRootDocument>();
|
var k = coll.Value.GenerateKey(e);
|
||||||
res.Add(_createXMLRootDocument(root, elem));
|
if (k != null) {
|
||||||
|
var searchtext = coll.Value.Searchable ?
|
||||||
|
StringHelpers.NormalizeWhiteSpace(e.ToString(), ' ', false) :
|
||||||
|
null;
|
||||||
|
var datafileds = coll.Value.GenerateDataFields != null ?
|
||||||
|
coll.Value.GenerateDataFields(e) :
|
||||||
|
null;
|
||||||
|
items[k] = new CollectedItem(k, e, coll.Value, datafileds, searchtext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (items.Any()) {
|
||||||
|
if (!ret.ContainsKey(coll.Key))
|
||||||
|
ret[coll.Key] = new ItemsCollection(coll.Key, coll.Value);
|
||||||
|
foreach (var item in items)
|
||||||
|
ret[coll.Key].Items.Add(item.Key, item.Value);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (res == null) ModelState.AddModelError("Error", "Kein zum Hamann-Briefe-Projekt passendes XML gefunden.");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<string, FileList?>? GetUsedDictionary()
|
|
||||||
=> this._Used;
|
|
||||||
|
|
||||||
|
|
||||||
// Adds a document and sets it to used
|
|
||||||
public void Use(XMLRootDocument doc) {
|
|
||||||
if (_Used == null) _Used = new Dictionary<string, FileList?>();
|
|
||||||
if (!_Used.ContainsKey(doc.Prefix)) _Used.Add(doc.Prefix, new FileList(doc.XMLRoot));
|
|
||||||
_Used[doc.Prefix]!.Add(doc);
|
|
||||||
_ = doc.GetElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnUse(string prefix) {
|
|
||||||
if (_Used != null && _Used.ContainsKey(prefix)) {
|
|
||||||
// Unload the Elements so unused files don't use up the memory.
|
|
||||||
if (_Used[prefix]!.GetFileList() != null) {
|
|
||||||
foreach (var e in _Used[prefix]!.GetFileList()) {
|
|
||||||
e.UnUse();
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
_Used.Remove(prefix);
|
|
||||||
|
if (ret.Any()) {
|
||||||
|
Parallel.ForEach(ret, (collection) => {
|
||||||
|
collection.Value.GenerateGroupings();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
_Collection = ret.ToDictionary(x => x.Key, y => y.Value);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performs detection of using on the specified document type
|
private IEnumerable<Type> _GetAllTypesThatImplementInterface<T>() {
|
||||||
public void AutoUse(string prefix) {
|
|
||||||
if (_Used == null || !_Used.ContainsKey(prefix)) return;
|
|
||||||
AutoUse(_Used[prefix]!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performs detection of using given a list of files
|
|
||||||
public void AutoUse(FileList filelist) {
|
|
||||||
FileList? res = null;
|
|
||||||
var list = filelist.GetFileList();
|
|
||||||
var prefix = filelist.XMLRoot.Prefix;
|
|
||||||
|
|
||||||
if (list == null) return;
|
|
||||||
if (_Used != null && _Used.ContainsKey(prefix)) _Used.Remove(prefix);
|
|
||||||
|
|
||||||
// TODO: Item1
|
|
||||||
var lookup = list.ToLookup(x => x.IdentificationString.Item2);
|
|
||||||
foreach (var idstring in lookup) {
|
|
||||||
var ordered = idstring.OrderBy(x => x.Date);
|
|
||||||
if (res == null) res = new FileList(filelist.XMLRoot);
|
|
||||||
Use(ordered.Last());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public XElement? MergeUsedDocuments(ModelStateDictionary ModelState) {
|
|
||||||
if (_Used == null || _Roots == null) {
|
|
||||||
ModelState.AddModelError("Error", "Keine Dokumente ausgewählt");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var opus = new XElement("opus");
|
|
||||||
// TODO: Workaround for bug in HaDocument: roots have to be added in a specific order
|
|
||||||
var used = _Used.OrderByDescending(x => x.Key);
|
|
||||||
foreach (var category in used) {
|
|
||||||
if (category.Value == null || category.Value.GetFileList() == null || !category.Value.GetFileList()!.Any()) {
|
|
||||||
ModelState.AddModelError("Error", _Roots![category.Key].Type + " nicht vorhanden.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var documents = category.Value.GetFileList();
|
|
||||||
foreach (var document in documents!) {
|
|
||||||
document.XMLRoot.MergeIntoFile(opus, document);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return opus;
|
|
||||||
}
|
|
||||||
|
|
||||||
private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element) {
|
|
||||||
var doc = new XMLRootDocument(Root, Root.Prefix, Root.GenerateIdentificationString(element), element);
|
|
||||||
doc.Fields = Root.GenerateFields(doc);
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<Type> _GetAllTypesThatImplementInterface<T>()
|
|
||||||
{
|
|
||||||
return System.Reflection.Assembly.GetExecutingAssembly()
|
return System.Reflection.Assembly.GetExecutingAssembly()
|
||||||
.GetTypes()
|
.GetTypes()
|
||||||
.Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface);
|
.Where(type => typeof(T).IsAssignableFrom(type) && !type.IsInterface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<XMLRootDocument>? _ProbeFile(XDocument document, FileModel file) {
|
||||||
|
if (document.Root!.Name != "opus") {
|
||||||
|
file.Log("Ein gültiges Dokument muss mit <opus> beginnen.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLRootDocument>? res = null;
|
||||||
|
if (document.Root != null && _RootDefs != null) {
|
||||||
|
foreach (var (_, root) in _RootDefs) {
|
||||||
|
var elements = root.IsTypeOf(document.Root);
|
||||||
|
if (elements != null && elements.Any())
|
||||||
|
foreach (var elem in elements) {
|
||||||
|
if (res == null) res = new List<XMLRootDocument>();
|
||||||
|
res.Add(_createXMLRootDocument(root, elem, file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (res == null) file.Log("Dokumenten-Typ nicht erkannt.");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element, FileModel file) {
|
||||||
|
var doc = new XMLRootDocument(Root, Root.Prefix, Root.GenerateIdentificationString(element), element, file);
|
||||||
|
doc.Fields = Root.GenerateFields(doc);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileModel _CreateFileModel(IFileInfo file, string? message) {
|
||||||
|
var m = new FileModel(file.Name, file);
|
||||||
|
if (!String.IsNullOrWhiteSpace(message)) {
|
||||||
|
m.Log(message);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
138
HaWeb/XMLTests/BackgroundServices.cs
Normal file
138
HaWeb/XMLTests/BackgroundServices.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
namespace HaWeb.BackgroundTask;
|
||||||
|
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
public interface IBackgroundTaskQueue {
|
||||||
|
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
|
||||||
|
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMonitorLoop {
|
||||||
|
public void StartMonitorLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BackgroundTaskQueue : IBackgroundTaskQueue {
|
||||||
|
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
|
||||||
|
|
||||||
|
public BackgroundTaskQueue(int capacity) {
|
||||||
|
var options = new BoundedChannelOptions(capacity) {
|
||||||
|
FullMode = BoundedChannelFullMode.Wait
|
||||||
|
};
|
||||||
|
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem) {
|
||||||
|
if (workItem == null) {
|
||||||
|
throw new ArgumentNullException(nameof(workItem));
|
||||||
|
}
|
||||||
|
await _queue.Writer.WriteAsync(workItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
|
||||||
|
CancellationToken cancellationToken
|
||||||
|
) {
|
||||||
|
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
|
||||||
|
return workItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class QueuedHostedService : BackgroundService {
|
||||||
|
private readonly ILogger<QueuedHostedService> _logger;
|
||||||
|
public IBackgroundTaskQueue TaskQueue { get; }
|
||||||
|
|
||||||
|
public QueuedHostedService(IBackgroundTaskQueue taskQueue, ILogger<QueuedHostedService> logger) {
|
||||||
|
TaskQueue = taskQueue;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken) {
|
||||||
|
await BackgroundProcessing(stoppingToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BackgroundProcessing(CancellationToken stoppingToken) {
|
||||||
|
while (!stoppingToken.IsCancellationRequested) {
|
||||||
|
var workItem = await TaskQueue.DequeueAsync(stoppingToken);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await workItem(stoppingToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
_logger.LogError(ex, "Error occurred executing {WorkItem}.", nameof(workItem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task StopAsync(CancellationToken stoppingToken) {
|
||||||
|
_logger.LogInformation("Queued Hosted Service is stopping.");
|
||||||
|
|
||||||
|
await base.StopAsync(stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MonitorLoop : IMonitorLoop {
|
||||||
|
private readonly IBackgroundTaskQueue _taskQueue;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly CancellationToken _cancellationToken;
|
||||||
|
|
||||||
|
public MonitorLoop(
|
||||||
|
IBackgroundTaskQueue taskQueue,
|
||||||
|
ILogger<MonitorLoop> logger,
|
||||||
|
IHostApplicationLifetime applicationLifetime
|
||||||
|
) {
|
||||||
|
_taskQueue = taskQueue;
|
||||||
|
_logger = logger;
|
||||||
|
_cancellationToken = applicationLifetime.ApplicationStopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartMonitorLoop() {
|
||||||
|
_logger.LogInformation("MonitorAsync Loop is starting.");
|
||||||
|
|
||||||
|
// Run a console user input loop in a background thread
|
||||||
|
Task.Run(async () => await MonitorAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask MonitorAsync() {
|
||||||
|
while (!_cancellationToken.IsCancellationRequested) {
|
||||||
|
var keyStroke = Console.ReadKey();
|
||||||
|
|
||||||
|
if (keyStroke.Key == ConsoleKey.W) {
|
||||||
|
// Enqueue a background work item
|
||||||
|
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ValueTask BuildWorkItem(CancellationToken token) {
|
||||||
|
// Simulate three 5-second tasks to complete
|
||||||
|
// for each enqueued work item
|
||||||
|
|
||||||
|
int delayLoop = 0;
|
||||||
|
var guid = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
|
||||||
|
|
||||||
|
while (!token.IsCancellationRequested && delayLoop < 3) {
|
||||||
|
try {
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(5), token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) {
|
||||||
|
// Prevent throwing if the Delay is cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
delayLoop++;
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"Queued Background Task {Guid} is running. " + "{DelayLoop}/3",
|
||||||
|
guid,
|
||||||
|
delayLoop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayLoop == 3) {
|
||||||
|
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
HaWeb/XMLTests/HamannXPath.cs
Normal file
6
HaWeb/XMLTests/HamannXPath.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace HaWeb.XMLTests;
|
||||||
|
|
||||||
|
public class HamannXPath {
|
||||||
|
public string[]? Documents { get; set; }
|
||||||
|
public string? XPath { get; set; }
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ using System.Xml.Linq;
|
|||||||
|
|
||||||
public interface ICollectionRule {
|
public interface ICollectionRule {
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string[] Bases { get; }
|
public HamannXPath[] Bases { get; }
|
||||||
public string[] Backlinks { get; }
|
public HamannXPath[] Backlinks { get; }
|
||||||
public IEnumerable<(string, XElement, XMLRootDocument)> GenerateIdentificationStrings(IEnumerable<(XElement, XMLRootDocument)> List);
|
public IEnumerable<(string, XElement, XMLRootDocument)> GenerateIdentificationStrings(IEnumerable<(XElement, XMLRootDocument)> List);
|
||||||
public IEnumerable<(string, XElement, XMLRootDocument, bool)> GenerateBacklinkString(IEnumerable<(XElement, XMLRootDocument)> List);
|
public IEnumerable<(string, XElement, XMLRootDocument, bool)> GenerateBacklinkString(IEnumerable<(XElement, XMLRootDocument)> List);
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using HaWeb.XMLParser;
|
||||||
|
|
||||||
namespace HaWeb.XMLTests;
|
namespace HaWeb.XMLTests;
|
||||||
|
|
||||||
public interface IXMLTestService {
|
public interface IXMLTestService {
|
||||||
@@ -5,5 +7,5 @@ public interface IXMLTestService {
|
|||||||
public Dictionary<string, INodeRule>? Ruleset { get; }
|
public Dictionary<string, INodeRule>? Ruleset { get; }
|
||||||
public Dictionary<string, ICollectionRule>? CollectionRuleset { get; }
|
public Dictionary<string, ICollectionRule>? CollectionRuleset { get; }
|
||||||
|
|
||||||
public void Test();
|
public void Test(IXMLInteractionService _XMLService);
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ namespace HaWeb.XMLTests;
|
|||||||
|
|
||||||
public interface INodeRule {
|
public interface INodeRule {
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public string XPath { get; }
|
public HamannXPath XPath { get; }
|
||||||
public string? uniquenessAttribute { get; }
|
public string? uniquenessAttribute { get; }
|
||||||
public List<(string LinkAttribute, string RemoteElement, string RemoteAttribute)>? References { get; }
|
public List<(string LinkAttribute, HamannXPath RemoteElement, string RemoteAttribute)>? References { get; }
|
||||||
public string[]? Attributes { get; }
|
public string[]? Attributes { get; }
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,9 @@ namespace HaWeb.XMLTests;
|
|||||||
using HaWeb.XMLParser;
|
using HaWeb.XMLParser;
|
||||||
|
|
||||||
public class XMLTestService : IXMLTestService {
|
public class XMLTestService : IXMLTestService {
|
||||||
private IXMLService _XMLService;
|
|
||||||
public Dictionary<string, INodeRule>? Ruleset { get; private set; }
|
public Dictionary<string, INodeRule>? Ruleset { get; private set; }
|
||||||
public Dictionary<string, ICollectionRule>? CollectionRuleset { get; private set; }
|
public Dictionary<string, ICollectionRule>? CollectionRuleset { get; private set; }
|
||||||
public XMLTestService(IXMLService xmlService) {
|
public XMLTestService() {
|
||||||
_XMLService = xmlService;
|
|
||||||
|
|
||||||
var roottypes = _GetAllTypesThatImplementInterface<INodeRule>().ToList();
|
var roottypes = _GetAllTypesThatImplementInterface<INodeRule>().ToList();
|
||||||
roottypes.ForEach( x => {
|
roottypes.ForEach( x => {
|
||||||
if (this.Ruleset == null) this.Ruleset = new Dictionary<string, INodeRule>();
|
if (this.Ruleset == null) this.Ruleset = new Dictionary<string, INodeRule>();
|
||||||
@@ -23,17 +20,10 @@ public class XMLTestService : IXMLTestService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Test() {
|
public void Test(IXMLInteractionService _XMLService) {
|
||||||
var docs = _XMLService.GetUsedDictionary();
|
var docs = _XMLService.GetLoaded();
|
||||||
if (docs == null) return;
|
if (docs == null) return;
|
||||||
foreach (var d in docs.Values) {
|
var tester = new XMLTester(this, docs);
|
||||||
var fl = d.GetFileList();
|
|
||||||
if (fl == null) continue;
|
|
||||||
foreach (var v in fl) {
|
|
||||||
v.ResetLog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var tester = new XMLTester(this, _XMLService.GetUsedDictionary());
|
|
||||||
tester.Test();
|
tester.Test();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public class XMLTester {
|
|||||||
|
|
||||||
public void Test() {
|
public void Test() {
|
||||||
if (_Ruleset == null) return;
|
if (_Ruleset == null) return;
|
||||||
_IDs = new System.Collections.Generic.Dictionary<string, HashSet<string>>();
|
_IDs = new Dictionary<string, HashSet<string>>();
|
||||||
foreach (var rule in _Ruleset) {
|
foreach (var rule in _Ruleset) {
|
||||||
buildIDs(rule.Value);
|
buildIDs(rule.Value);
|
||||||
checkRequiredAttributes(rule.Value);
|
checkRequiredAttributes(rule.Value);
|
||||||
@@ -42,6 +42,7 @@ public class XMLTester {
|
|||||||
checkReferences(collectionrule.Value);
|
checkReferences(collectionrule.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkReferences(INodeRule rule) {
|
private void checkReferences(INodeRule rule) {
|
||||||
if (rule.References == null || !rule.References.Any()) return;
|
if (rule.References == null || !rule.References.Any()) return;
|
||||||
var elements = GetEvaluateXPath(rule.XPath);
|
var elements = GetEvaluateXPath(rule.XPath);
|
||||||
@@ -49,11 +50,11 @@ public class XMLTester {
|
|||||||
foreach (var e in elements) {
|
foreach (var e in elements) {
|
||||||
foreach (var r in rule.References) {
|
foreach (var r in rule.References) {
|
||||||
var hasattr = checkAttribute(e.Item1, r.LinkAttribute, e.Item2, false);
|
var hasattr = checkAttribute(e.Item1, r.LinkAttribute, e.Item2, false);
|
||||||
var keyname = r.RemoteElement + "-" + r.RemoteAttribute;
|
var keyname = r.RemoteElement.XPath + "-" + r.RemoteAttribute;
|
||||||
if (_IDs != null && _IDs.ContainsKey(keyname) && hasattr) {
|
if (_IDs != null && _IDs.ContainsKey(keyname) && hasattr) {
|
||||||
var val = e.Item1.Attribute(r.LinkAttribute)!.Value;
|
var val = e.Item1.Attribute(r.LinkAttribute)!.Value;
|
||||||
if (!_IDs[keyname].Contains(val)) {
|
if (!_IDs[keyname].Contains(val)) {
|
||||||
e.Item2.Log(generateLogMessage(e.Item1) + "Verlinktes Element " + val + " nicht gefunden.");
|
e.Item2.File.Log(generateLogMessage(e.Item1) + "Verlinktes Element " + val + " nicht gefunden.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,7 +69,7 @@ public class XMLTester {
|
|||||||
if (elemens != null && elemens.Any()) {
|
if (elemens != null && elemens.Any()) {
|
||||||
foreach(var r in rule.GenerateBacklinkString(elemens)) {
|
foreach(var r in rule.GenerateBacklinkString(elemens)) {
|
||||||
if (!r.Item4 && !_CollectionIDs[rule.Name].Contains(r.Item1)) {
|
if (!r.Item4 && !_CollectionIDs[rule.Name].Contains(r.Item1)) {
|
||||||
r.Item3.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
r.Item3.File.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
||||||
}
|
}
|
||||||
if (r.Item4) {
|
if (r.Item4) {
|
||||||
var coll = _CollectionIDs[rule.Name];
|
var coll = _CollectionIDs[rule.Name];
|
||||||
@@ -76,7 +77,7 @@ public class XMLTester {
|
|||||||
var searchterm = items[0];
|
var searchterm = items[0];
|
||||||
var found = coll.Where(x => x.StartsWith(searchterm));
|
var found = coll.Where(x => x.StartsWith(searchterm));
|
||||||
if (items[0] == "NA" || found == null || !found.Any()) {
|
if (items[0] == "NA" || found == null || !found.Any()) {
|
||||||
r.Item3.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
r.Item3.File.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
||||||
} else {
|
} else {
|
||||||
for (var i = 1; i < items.Length; i++) {
|
for (var i = 1; i < items.Length; i++) {
|
||||||
if (items[i] == "NA") break;
|
if (items[i] == "NA") break;
|
||||||
@@ -84,7 +85,7 @@ public class XMLTester {
|
|||||||
searchterm = searchterm + "-" + items[i];
|
searchterm = searchterm + "-" + items[i];
|
||||||
found = found.Where(x => x.StartsWith(searchterm));
|
found = found.Where(x => x.StartsWith(searchterm));
|
||||||
if (found == null || !found.Any())
|
if (found == null || !found.Any())
|
||||||
r.Item3.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
r.Item3.File.Log(generateLogMessage(r.Item2) + "Verlinktes Element " + r.Item1 + " nicht gefunden.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +126,7 @@ public class XMLTester {
|
|||||||
if (elemens != null && elemens.Any()) {
|
if (elemens != null && elemens.Any()) {
|
||||||
foreach (var r in rule.GenerateIdentificationStrings(elemens)) {
|
foreach (var r in rule.GenerateIdentificationStrings(elemens)) {
|
||||||
if (!hs.Add(r.Item1)) {
|
if (!hs.Add(r.Item1)) {
|
||||||
r.Item3.Log(generateLogMessage(r.Item2) + "Brief-Seite-Zeile " + r.Item1 + " mehrdeutig.");
|
r.Item3.File.Log(generateLogMessage(r.Item2) + "Brief-Seite-Zeile " + r.Item1 + " mehrdeutig.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,24 +135,24 @@ public class XMLTester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkUniqueness(string xpathelement, string attribute) {
|
private void checkUniqueness(HamannXPath xpathelement, string attribute) {
|
||||||
if (_Documents == null || _IDs == null || _IDs.ContainsKey(xpathelement + "-" + attribute)) return;
|
if (_Documents == null || _IDs == null || _IDs.ContainsKey(xpathelement.XPath + "-" + attribute)) return;
|
||||||
var hs = new HashSet<string>();
|
var hs = new HashSet<string>();
|
||||||
var elements = GetEvaluateXPath(xpathelement);
|
var elements = GetEvaluateXPath(xpathelement);
|
||||||
if (elements != null)
|
if (elements != null)
|
||||||
foreach (var e in elements) {
|
foreach (var e in elements) {
|
||||||
if (checkAttribute(e.Item1, attribute, e.Item2)) {
|
if (checkAttribute(e.Item1, attribute, e.Item2)) {
|
||||||
if (!hs.Add(e.Item1.Attribute(attribute)!.Value)) {
|
if (!hs.Add(e.Item1.Attribute(attribute)!.Value)) {
|
||||||
e.Item2.Log(generateLogMessage(e.Item1) + "Attributwert " + e.Item1.Attribute(attribute)!.Value + " doppelt.");
|
e.Item2.File.Log(generateLogMessage(e.Item1) + "Attributwert " + e.Item1.Attribute(attribute)!.Value + " doppelt.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_IDs.TryAdd(xpathelement + "-" + attribute, hs);
|
_IDs.TryAdd(xpathelement.XPath + "-" + attribute, hs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool checkAttribute(XElement element, string attributename, XMLRootDocument doc, bool log = true) {
|
private bool checkAttribute(XElement element, string attributename, XMLRootDocument doc, bool log = true) {
|
||||||
if (!element.HasAttributes || element.Attribute(attributename) == null) {
|
if (!element.HasAttributes || element.Attribute(attributename) == null) {
|
||||||
if (log) doc.Log(generateLogMessage(element) + "Attribut " + attributename + " fehlt.");
|
if (log) doc.File.Log(generateLogMessage(element) + "Attribut " + attributename + " fehlt.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -165,19 +166,21 @@ public class XMLTester {
|
|||||||
": ";
|
": ";
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(XElement, XMLRootDocument)>? GetEvaluateXPath(string xpath) {
|
// Cache for XPATH evaluation
|
||||||
if (_XPathEvaluated.ContainsKey(xpath)) return _XPathEvaluated[xpath];
|
private List<(XElement, XMLRootDocument)>? GetEvaluateXPath(HamannXPath xpath) {
|
||||||
if (!_XPathEvaluated.ContainsKey(xpath)) _XPathEvaluated.Add(xpath, null);
|
if (_Documents == null || xpath == null) return null;
|
||||||
if (_Documents == null) return null;
|
if (_XPathEvaluated.ContainsKey(xpath.XPath)) return _XPathEvaluated[xpath.XPath];
|
||||||
|
if (!_XPathEvaluated.ContainsKey(xpath.XPath)) _XPathEvaluated.Add(xpath.XPath, null);
|
||||||
foreach (var d in _Documents) {
|
foreach (var d in _Documents) {
|
||||||
var elements = d.GetElement().XPathSelectElements(xpath).ToList();
|
if (xpath.Documents != null && !xpath.Documents.Contains(d.Prefix)) continue;
|
||||||
|
var elements = d.Element.XPathSelectElements("." + xpath.XPath).ToList();
|
||||||
if (elements != null && elements.Any()) {
|
if (elements != null && elements.Any()) {
|
||||||
if (_XPathEvaluated[xpath] == null) _XPathEvaluated[xpath] = new List<(XElement, XMLRootDocument)>();
|
if (_XPathEvaluated[xpath.XPath] == null) _XPathEvaluated[xpath.XPath] = new List<(XElement, XMLRootDocument)>();
|
||||||
foreach (var res in elements) {
|
foreach (var res in elements) {
|
||||||
_XPathEvaluated[xpath]!.Add((res, d));
|
_XPathEvaluated[xpath.XPath]!.Add((res, d));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _XPathEvaluated[xpath];
|
return _XPathEvaluated[xpath.XPath];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,14 +7,16 @@
|
|||||||
},
|
},
|
||||||
"FeatureManagement": {
|
"FeatureManagement": {
|
||||||
"AdminService": true,
|
"AdminService": true,
|
||||||
"UploadService": true,
|
"LocalPublishService": true
|
||||||
"LocalPublishService": true,
|
|
||||||
"RemotePublishService": false,
|
|
||||||
"RemotePublishSourceService": false
|
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"StoredFilePathLinux": "/home/simon/Downloads/test/",
|
"HamannFileStoreLinux": "/home/simon/Downloads/test/",
|
||||||
"StoredFilePathWindows": "C:/Users/simon/Downloads/test/",
|
"HamannFileStoreWindows": "C:/Users/simon/Downloads/test/",
|
||||||
|
"BareRepositoryPathLinux": "/home/simon/Downloads/test/",
|
||||||
|
"BareRepositoryPathWindows": "C:/Users/simon/source/hamann-xml/.git/",
|
||||||
|
"WorkingTreePathLinux": "/home/simon/Downloads/test/",
|
||||||
|
"WorkingTreePathWindows": "C:/Users/simon/source/hamann-xml/",
|
||||||
|
"RepositoryBranch": "main",
|
||||||
"StoredPDFPathWindows": "",
|
"StoredPDFPathWindows": "",
|
||||||
"StoredPDFPathLinux": "",
|
"StoredPDFPathLinux": "",
|
||||||
"FileSizeLimit": 52428800,
|
"FileSizeLimit": 52428800,
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
{
|
|
||||||
"FormattingOptions": {
|
|
||||||
"NewLinesForBracesInLambdaExpressionBody": false,
|
|
||||||
"NewLinesForBracesInAnonymousMethods": false,
|
|
||||||
"NewLinesForBracesInAnonymousTypes": false,
|
|
||||||
"NewLinesForBracesInControlBlocks": false,
|
|
||||||
"NewLinesForBracesInTypes": false,
|
|
||||||
"NewLinesForBracesInMethods": false,
|
|
||||||
"NewLinesForBracesInProperties": false,
|
|
||||||
"NewLinesForBracesInObjectCollectionArrayInitializers": false,
|
|
||||||
"NewLinesForBracesInAccessors": false,
|
|
||||||
"NewLineForElse": false,
|
|
||||||
"NewLineForCatch": false,
|
|
||||||
"NewLineForFinally": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
|
function getCookie(name) {
|
||||||
|
var value = "; " + document.cookie;
|
||||||
|
var parts = value.split("; " + name + "=");
|
||||||
|
if (parts.length == 2) return parts.pop().split(";").shift();
|
||||||
|
}
|
||||||
|
|
||||||
const USESubmit = async function (oFormElement, file = null) {
|
const USESubmit = async function (oFormElement, file = null) {
|
||||||
let fd = new FormData(oFormElement);
|
let fd = new FormData(oFormElement);
|
||||||
document.getElementById("ha-filelistbutton").style.pointerEvents = "none";
|
document.getElementById("ha-filelistbutton").style.pointerEvents = "none";
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
const hideshowfiles = function() {
|
|
||||||
let elem = document.getElementById("ha-availablefileslist");
|
|
||||||
if (elem.classList.contains('hidden')) {
|
|
||||||
|
|
||||||
elem.classList.remove('hidden');
|
|
||||||
elem.classList.add('block');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
elem.classList.add('hidden');
|
|
||||||
elem.classList.remove('block');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
var value = "; " + document.cookie;
|
|
||||||
var parts = value.split("; " + name + "=");
|
|
||||||
if (parts.length == 2) return parts.pop().split(";").shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
var filesbutton = document.getElementById("ha-availablefiles");
|
|
||||||
if (filesbutton !== null)
|
|
||||||
filesbutton.addEventListener("click", () => hideshowfiles());
|
|
||||||
Reference in New Issue
Block a user