diff --git a/HaWeb/Controllers/UploadController.cs b/HaWeb/Controllers/UploadController.cs index ef7d529..b7c8473 100644 --- a/HaWeb/Controllers/UploadController.cs +++ b/HaWeb/Controllers/UploadController.cs @@ -18,6 +18,8 @@ using HaWeb.FileHelpers; using HaWeb.XMLParser; using HaWeb.Models; using System.Xml.Linq; +using System.Text.Json.Serialization; +using System.Text.Json; public class UploadController : Controller { @@ -109,18 +111,36 @@ public class UploadController : Controller if (!ModelState.IsValid || xdocument == null) return UnprocessableEntity(ModelState); -//// 3. Stage: Is it a Hamann-Document? What kind? +//// 4. Stage: Is it a Hamann-Document? What kind? var docs = _xmlService.ProbeHamannFile(xdocument, ModelState); if (!ModelState.IsValid || docs == null || !docs.Any()) return UnprocessableEntity(ModelState); //// 5. Stage: Saving the File(s) foreach (var doc in docs) { - using (var targetStream = System.IO.File.Create(Path.Combine(_targetFilePath, doc.CreateFilename()))) - doc.Save(targetStream); + var type = doc.Prefix; + var directory = Path.Combine(_targetFilePath, type); + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + var path = Path.Combine(directory, doc.FileName); + try { + using (var targetStream = System.IO.File.Create(path)) + await doc.Save(targetStream, ModelState); + if (!ModelState.IsValid) return StatusCode(500, ModelState); + } + catch (Exception ex) { + ModelState.AddModelError("Error", "Speichern der Datei fehlgeschlagen: " + ex.Message); + return StatusCode(500, ModelState); + } } - return Created(nameof(UploadController), docs); +// 6. State: Returning Ok, and redirecting + JsonSerializerOptions options = new() { + ReferenceHandler = ReferenceHandler.Preserve + }; + + string json = JsonSerializer.Serialize(docs); + return Created(nameof(UploadController), json); } try diff --git a/HaWeb/Settings/XMLRoots/CommentRoot.cs b/HaWeb/Settings/XMLRoots/CommentRoot.cs index c434cfc..6f31e0d 100644 --- a/HaWeb/Settings/XMLRoots/CommentRoot.cs +++ b/HaWeb/Settings/XMLRoots/CommentRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class CommentRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Register"; - public string Container { get; } = "kommcat"; + public string Prefix { get; } = "register"; + public string[] XPathContainer { get; } = { ".//data//kommentare/kommcat", ".//kommentare/kommcat" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "kommentar") return true; @@ -18,19 +19,27 @@ public class CommentRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { + public (string?, string?) GenerateIdentificationString(XElement element) { var kat = element.Attribute("value"); if (kat != null && !String.IsNullOrWhiteSpace(kat.Value)) return (null, kat.Value); - return (null, Container); + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + var kommentare = new XElement("kommentare"); + kommentare.AddFirst(element); + opus.AddFirst(kommentare); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/DescriptionsRoot.cs b/HaWeb/Settings/XMLRoots/DescriptionsRoot.cs index 64bfa90..4ea219a 100644 --- a/HaWeb/Settings/XMLRoots/DescriptionsRoot.cs +++ b/HaWeb/Settings/XMLRoots/DescriptionsRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Metadaten"; - public string Container { get; } = "descriptions"; + public string Prefix { get; } = "metadaten"; + public string[] XPathContainer { get; } = {".//data/descriptions", ".//descriptions" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "letterDesc") return true; @@ -18,16 +19,22 @@ public class DescriptionsRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/DocumentRoot.cs b/HaWeb/Settings/XMLRoots/DocumentRoot.cs index f721188..b8001f4 100644 --- a/HaWeb/Settings/XMLRoots/DocumentRoot.cs +++ b/HaWeb/Settings/XMLRoots/DocumentRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class DocumentRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Brieftext"; - public string Container { get; } = "document"; + public string Prefix { get; } = "brieftext"; + public string[] XPathContainer { get; } = { ".//data/document", ".//document" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "letterText") return true; @@ -18,16 +19,22 @@ public class DocumentRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/EditsRoot.cs b/HaWeb/Settings/XMLRoots/EditsRoot.cs index 99e2971..c117340 100644 --- a/HaWeb/Settings/XMLRoots/EditsRoot.cs +++ b/HaWeb/Settings/XMLRoots/EditsRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class EditsRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Texteingriffe"; - public string Container { get; } = "edits"; + public string Prefix { get; } = "texteingriffe"; + public string[] XPathContainer { get; } = { ".//data/edits", ".//edits" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "editreason") return true; @@ -18,16 +19,22 @@ public class EditsRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/MarginalsRoot.cs b/HaWeb/Settings/XMLRoots/MarginalsRoot.cs index 8336688..bace12c 100644 --- a/HaWeb/Settings/XMLRoots/MarginalsRoot.cs +++ b/HaWeb/Settings/XMLRoots/MarginalsRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Stellenkommentar"; - public string Container { get; } = "marginalien"; + public string Prefix { get; } = "stellenkommentar"; + public string[] XPathContainer { get; } = { ".//data/marginalien", ".//marginalien" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "marginal") return true; @@ -18,16 +19,22 @@ public class MarginalsRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/ReferencesRoot.cs b/HaWeb/Settings/XMLRoots/ReferencesRoot.cs index 0b31d26..fbf7671 100644 --- a/HaWeb/Settings/XMLRoots/ReferencesRoot.cs +++ b/HaWeb/Settings/XMLRoots/ReferencesRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Personen / Orte"; - public string Container { get; } = "definitions"; + public string Prefix { get; } = "personenorte"; + public string[] XPathContainer { get; } = { ".//data/definitions", ".//definitions" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "personDefs" || elem.Name == "structureDefs" || elem.Name == "handDefs" || elem.Name == "locationDefs") @@ -16,16 +17,22 @@ public class ReferencesRoot : HaWeb.XMLParser.IXMLRoot { return elem.Name.ToString(); }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/Settings/XMLRoots/TraditionsRoot.cs b/HaWeb/Settings/XMLRoots/TraditionsRoot.cs index 7b577a0..5675a6c 100644 --- a/HaWeb/Settings/XMLRoots/TraditionsRoot.cs +++ b/HaWeb/Settings/XMLRoots/TraditionsRoot.cs @@ -4,7 +4,8 @@ using HaWeb.XMLParser; public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot { public string Type { get; } = "Überlieferung"; - public string Container { get; } = "traditions"; + public string Prefix { get; } = "ueberlieferung"; + public string[] XPathContainer { get; } = { ".//data/traditions", ".//traditions" }; public Predicate IsCollectedObject { get; } = (elem) => { if (elem.Name == "letterTradition") return true; @@ -18,16 +19,22 @@ public class TraditionsRoot : HaWeb.XMLParser.IXMLRoot { else return null; }; - public List<(string, string)>? GenerateFields(XMLRootDocument document) { + public List<(string, string?)>? GenerateFields(XMLRootDocument document) { return null; } - public (string?, string) GenerateIdentificationString(XElement element) { - return (null, Container); + public (string?, string?) GenerateIdentificationString(XElement element) { + return (null, null); } public bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2) { return true; } + public XElement CreateHamannDocument(XElement element) { + var opus = new XElement("opus"); + opus.AddFirst(element); + return opus; + } + } \ No newline at end of file diff --git a/HaWeb/XMLParser/IXMLRoot.cs b/HaWeb/XMLParser/IXMLRoot.cs index 6f0f337..7fe3b81 100644 --- a/HaWeb/XMLParser/IXMLRoot.cs +++ b/HaWeb/XMLParser/IXMLRoot.cs @@ -1,12 +1,16 @@ namespace HaWeb.XMLParser; using System.Xml.Linq; +using System.Xml.XPath; public interface IXMLRoot { // Name of the IXMLRoot public abstract string Type { get; } - // Tag Name of the Container - public abstract string Container { get; } + // Name of the file prefix + public abstract string Prefix { get; } + + // XPaths to determine if container is present + public abstract string[] XPathContainer { get; } // Tag Name of child objects to be collected public abstract Predicate IsCollectedObject { get; } @@ -14,20 +18,27 @@ public interface IXMLRoot { // Gets the Key of a collected object public abstract Func GetKey { get; } - // Is the pesented XElement such a root? - public bool IsTypeOf(XElement xelement) { - if (xelement.Name == this.Container) return true; - return false; + // Can the Root be found within that document? + public List? IsTypeOf(XElement root) { + List? ret = null; + foreach (var p in XPathContainer) { + var elements = root.XPathSelectElements(p); + if (elements != null && elements.Any()) { + if (ret == null) ret = new List(); + ret.AddRange(elements); + } + } + return ret; } // Generate certain metadat fields to display about this root - public abstract List<(string, string)>? GenerateFields(XMLRootDocument document); + public abstract List<(string, string?)>? GenerateFields(XMLRootDocument document); // Generate an identification string of which the hash will be the filename. // The second string will be appended literally for convenience. // If the queries of two document are equal they replace each other // If the queries and the date of two documents are equal the later one gets deleted - public abstract (string?, string) GenerateIdentificationString(XElement element); + public abstract (string?, string?) GenerateIdentificationString(XElement element); // Further deciding which of two documents replaces which public abstract bool Replaces(XMLRootDocument doc1, XMLRootDocument doc2); @@ -44,4 +55,6 @@ public interface IXMLRoot { }); return ret; } + + public abstract XElement CreateHamannDocument(XElement element); } \ No newline at end of file diff --git a/HaWeb/XMLParser/IXMLService.cs b/HaWeb/XMLParser/IXMLService.cs index a8ec752..769b317 100644 --- a/HaWeb/XMLParser/IXMLService.cs +++ b/HaWeb/XMLParser/IXMLService.cs @@ -3,6 +3,7 @@ using System.Xml.Linq; using Microsoft.AspNetCore.Mvc.ModelBinding; public interface IXMLService { + public IXMLRoot? GetRoot(string name); public List? GetRoots(); public List? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState); } \ No newline at end of file diff --git a/HaWeb/XMLParser/XMLRootDocument.cs b/HaWeb/XMLParser/XMLRootDocument.cs index 7a222fd..af834e2 100644 --- a/HaWeb/XMLParser/XMLRootDocument.cs +++ b/HaWeb/XMLParser/XMLRootDocument.cs @@ -1,28 +1,63 @@ namespace HaWeb.XMLParser; using System.Xml.Linq; +using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Mvc.ModelBinding; public class XMLRootDocument { - public string Type {get; private set; } - public DateTime Date { get; private set; } - public XElement Root { get; private set; } - public (string?, string) IdentificationString { get; private set; } + private XElement? _Element; + private string? _filename; + private string? _path; + private IXMLService _xmlService; - public Dictionary? Elements { get; set; } + [JsonIgnore] + public XElement Root { + get { + if (_Element == null) { + _Element = GetElement(); + } + return _Element; + } } + + public string FileName { get { + if (_filename == null) + _filename = _CreateFilename(); + return _filename; + } } + + public string Prefix { get; private set; } + public DateTime Date { get; private set; } + + public (string?, string?) IdentificationString { get; private set; } + [JsonIgnore] public List<(string, string)>? Fields { get; set; } - public XMLRootDocument(string type, (string?, string) idString, XElement root) { - Type = type; + // Entry point for file reading + public XMLRootDocument(IXMLService xmlService, string prefix, (string?, string?) idString, DateTime date, string path) { + _xmlService = xmlService; + _path = path; + Prefix = prefix; IdentificationString = idString; - Date = DateTime.Today; - Root = root; + Date = date; } - public string CreateFilename() { - var filename = _removeInvalidChars(Type) + "_"; - if (IdentificationString.Item1 != null) filename += _removeInvalidChars(IdentificationString.Item1) + "_"; - filename += _removeInvalidChars(IdentificationString.Item2) + "_"; + // Entry point for XML upload reading + public XMLRootDocument(IXMLService xmlService, string prefix, (string?, string?) idString, XElement element) { + _xmlService = xmlService; + Prefix = prefix; + IdentificationString = idString; + Date = DateTime.Today; + _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()); - return filename; + return filename + ".xml"; } private string _removeInvalidChars(string? s) { @@ -30,12 +65,42 @@ public class XMLRootDocument { foreach (var c in Path.GetInvalidFileNameChars()) { s = s.Replace(c, '-'); } + s = s.Replace('_', '-'); return s; } - public async void Save(Stream stream) { - var nr = new XElement("opus"); - nr.AddFirst(Root); - await nr.SaveAsync(stream, SaveOptions.DisableFormatting, new CancellationToken()); + private XElement GetElement() { + if (_path == null || String.IsNullOrWhiteSpace(_path)) + throw new Exception("Es ist kein Pfad für die XML-Datei vorhanden."); + + var root = _xmlService.GetRoot(Prefix); + if (root == null) + throw new Exception("Kein gültiges Hamann-Dokument: " + _path + "Vom Prefix: " + Prefix); + + XDocument? doc = null; + try { + doc = XDocument.Load(_path, 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: " + _path); + + var element = root.IsTypeOf(doc.Root); + if (element == null || !element.Any()) + throw new Exception("Kein gültiges Hamann-Dokument: " + _path + "Vom Prefix: " + Prefix); + + return element.First(); + } + + public async Task Save(Stream stream, ModelStateDictionary state) { + var root = _xmlService.GetRoot(Prefix); + if (root == null) { + state.AddModelError("Error", "No corresponding Root Element found."); + return; + } + await root.CreateHamannDocument(Root).SaveAsync(stream, SaveOptions.DisableFormatting, new CancellationToken()); } } \ No newline at end of file diff --git a/HaWeb/XMLParser/XMLService.cs b/HaWeb/XMLParser/XMLService.cs index 3648cb0..c9d910b 100644 --- a/HaWeb/XMLParser/XMLService.cs +++ b/HaWeb/XMLParser/XMLService.cs @@ -11,10 +11,15 @@ public class XMLService : IXMLService { types.ForEach( x => { if (this._Roots == null) this._Roots = new Dictionary(); var instance = (IXMLRoot)Activator.CreateInstance(x)!; - if (instance != null) this._Roots.Add(instance.Type, instance); + if (instance != null) this._Roots.Add(instance.Prefix, instance); }); } + public IXMLRoot? GetRoot(string name) { + _Roots.TryGetValue(name, out var root); + return root; + } + public List? GetRoots() => this._Roots == null ? null : this._Roots.Values.ToList(); public List? ProbeHamannFile(XDocument document, ModelStateDictionary ModelState) { @@ -23,41 +28,23 @@ public class XMLService : IXMLService { return null; } - var res = _testElements(document.Root.Elements()); - if (document.Root.Element("data") != null) { - var datares = _testElements(document.Element("data")!.Elements()); - if (datares != null && res == null) res = datares; - else if (datares != null) res!.AddRange(datares); - } - - return res; - } - - private List? _testElements(IEnumerable? elements) { - if (elements == null) return null; List? res = null; - foreach (var elem in elements) { - var doc = _testElement(elem); - if (doc != null) { - if (res == null) res = new List(); - res.Add(doc); - } + if (document.Root != null && _Roots != null) { + foreach (var (_, root) in _Roots) { + var elements = root.IsTypeOf(document.Root); + if (elements != null && elements.Any()) + foreach (var elem in elements) { + if (res == null) res = new List(); + res.Add(_createXMLRootDocument(root, elem)); + } + } } + if (res == null) ModelState.AddModelError("Error", "Kein zum Hamann-Briefe-Projekt passendes XML gefunden."); return res; } - private XMLRootDocument? _testElement(XElement? element) { - if (element == null || _Roots == null) return null; - foreach (var (_, root) in _Roots) { - if(root.IsTypeOf(element)) - return _createXMLRootDocument(root, element); - } - return null; - } - private XMLRootDocument _createXMLRootDocument(IXMLRoot Root, XElement element) { - var doc = new XMLRootDocument(Root.Type, Root.GenerateIdentificationString(element), element); - doc.Elements = Root.GetCollectedObjects(doc); + var doc = new XMLRootDocument(this, Root.Prefix, Root.GenerateIdentificationString(element), element); doc.Fields = Root.GenerateFields(doc); return doc; } diff --git a/HaWeb/appsettings.json b/HaWeb/appsettings.json index 2ab3555..4b28723 100644 --- a/HaWeb/appsettings.json +++ b/HaWeb/appsettings.json @@ -11,7 +11,7 @@ "UpdateService": false }, "AllowedHosts": "*", - "StoredFilesPathLinux": "/home/simon/Downloads/", + "StoredFilePathLinux": "/home/simon/Downloads/test/", "StoredFilePathWindows": "D:/test/", "FileSizeLimit": 52428800 } diff --git a/XML/XML/test_empty.xml b/XML/XML/test_empty.xml new file mode 100644 index 0000000..8192c21 --- /dev/null +++ b/XML/XML/test_empty.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file