diff --git a/HaDocumentV6/Interfaces/ISearchable.cs b/HaDocumentV6/Interfaces/ISearchable.cs new file mode 100644 index 0000000..a10caff --- /dev/null +++ b/HaDocumentV6/Interfaces/ISearchable.cs @@ -0,0 +1,6 @@ +namespace HaDocument.Interfaces; + +public interface ISearchable { + public string Element { get; } + public string Index { get; } +} \ No newline at end of file diff --git a/HaDocumentV6/Models/Comment.cs b/HaDocumentV6/Models/Comment.cs index d072eef..a1293d0 100644 --- a/HaDocumentV6/Models/Comment.cs +++ b/HaDocumentV6/Models/Comment.cs @@ -2,9 +2,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -namespace HaDocument.Models { - public class Comment{ - public string Entry { get; } = ""; +namespace HaDocument.Models{ + public class Comment : HaDocument.Interfaces.ISearchable { + public string Element { get; } = ""; public string Index { get; } = ""; public string Type { get; } = ""; public string Lemma { get; } = ""; @@ -21,7 +21,7 @@ namespace HaDocument.Models { SortedDictionary subComments, string parent="" ) { - Entry = entry; + Element = entry; Index = index; Type = type; Lemma = lemma; diff --git a/HaDocumentV6/Models/Editreason.cs b/HaDocumentV6/Models/Editreason.cs index 0a974a5..895d1e6 100644 --- a/HaDocumentV6/Models/Editreason.cs +++ b/HaDocumentV6/Models/Editreason.cs @@ -1,5 +1,5 @@ namespace HaDocument.Models { - public class Editreason { + public class Editreason : HaDocument.Interfaces.ISearchable { public string Index { get; } = ""; public string Element { get; } = ""; public string Letter { get; } = ""; diff --git a/HaDocumentV6/Models/Letter.cs b/HaDocumentV6/Models/Letter.cs index 945b2db..26bd63e 100644 --- a/HaDocumentV6/Models/Letter.cs +++ b/HaDocumentV6/Models/Letter.cs @@ -1,5 +1,5 @@ namespace HaDocument.Models { - public class Letter : HaModel { + public class Letter : HaModel, HaDocument.Interfaces.ISearchable { public string Index { get; } = ""; public string Element { get; } = ""; diff --git a/HaDocumentV6/Models/Marginal.cs b/HaDocumentV6/Models/Marginal.cs index d536a77..0ca6bc5 100644 --- a/HaDocumentV6/Models/Marginal.cs +++ b/HaDocumentV6/Models/Marginal.cs @@ -1,5 +1,5 @@ namespace HaDocument.Models { - public class Marginal { + public class Marginal : HaDocument.Interfaces.ISearchable { public string Index { get; } = ""; public string Letter { get; } = ""; public string Page { get; } = ""; diff --git a/HaDocumentV6/Models/Tradition.cs b/HaDocumentV6/Models/Tradition.cs index e7154ff..2bbf85f 100644 --- a/HaDocumentV6/Models/Tradition.cs +++ b/HaDocumentV6/Models/Tradition.cs @@ -1,5 +1,5 @@ namespace HaDocument.Models { - public class Tradition { + public class Tradition : HaDocument.Interfaces.ISearchable { public string Index { get; } = ""; public string Element { get; } = ""; diff --git a/HaDocumentV6/Reactors/MetaReactor.cs b/HaDocumentV6/Reactors/MetaReactor.cs index 54c90be..766bb1f 100644 --- a/HaDocumentV6/Reactors/MetaReactor.cs +++ b/HaDocumentV6/Reactors/MetaReactor.cs @@ -156,8 +156,6 @@ namespace HaDocument.Reactors { (_availableVolumes == null && _availableYearRange.Item1 == 0 && _availableYearRange.Item2 == 0) ) { var ZHInfo = !inZH ? null : new ZHInfo(AltLineNumbering, dateChanged, Volume, Page); - if (Autopsic == "0") - System.Diagnostics.Debugger.Break(); var meta = new Meta( Index, Autopsic, diff --git a/HaWeb/Controllers/SucheController.cs b/HaWeb/Controllers/SucheController.cs index 4632a48..d4b1973 100644 --- a/HaWeb/Controllers/SucheController.cs +++ b/HaWeb/Controllers/SucheController.cs @@ -4,16 +4,19 @@ using HaWeb.Models; using HaWeb.FileHelpers; using HaDocument.Interfaces; using HaDocument.Models; +using HaXMLReader.Interfaces; using System.Collections.Specialized; namespace HaWeb.Controllers; public class SucheController : Controller { private IHaDocumentWrappper _lib; + private IReaderService _readerService; private int _lettersForPage; - public SucheController(IHaDocumentWrappper lib, IConfiguration config) { + public SucheController(IHaDocumentWrappper lib, IReaderService readerService, IConfiguration config) { _lib = lib; + _readerService = readerService; _lettersForPage = config.GetValue("LettersOnPage"); } @@ -48,21 +51,45 @@ public class SucheController : Controller { [Route("Suche")] // Order of actions: // Filter, sort by year, paginate, sort by Meta.Sort & .Order, parse - public IActionResult Index(string? person, int page = 0) { + public IActionResult Index(string? search, int page = 0) { var lib = _lib.GetLibrary(); List>? metasbyyear = null; - if (person != null) { - var letters = lib.Metas - .Where(x => x.Value.Senders.Contains(person) || x.Value.Receivers.Contains(person)) - .Select(x => x.Value); - if (letters == null) return _error404(); - metasbyyear = letters.ToLookup(x => x.Sort.Year).OrderBy(x => x.Key).ToList(); - } else { - metasbyyear = lib.MetasByYear.OrderBy(x => x.Key).ToList(); + if (search != null) { + var sw = new System.Diagnostics.Stopwatch(); + sw.Start(); + var res = _lib.SearchLetters(search, _readerService); + if (res == null || !res.Any()) return _error404(); + var ret = res.ToDictionary( + x => x.Index, + x => x.Results + .Select(y => new SearchResult(search, x.Index) { Page = y.Page, Line = y.Line, Preview = y.Preview}) + .ToList() + ); + var keys = res.Select(x => x.Index).Where(x => lib.Metas.ContainsKey(x)).Select(x => lib.Metas[x]); + var letters = keys.ToLookup(x => x.Sort.Year).OrderBy(x => x.Key).ToList(); + sw.Stop(); + Console.WriteLine(sw.ElapsedMilliseconds); + return _paginateSend(lib, page, letters, null, null, null, search, ret); + } + metasbyyear = lib.MetasByYear.OrderBy(x => x.Key).ToList(); + return _paginateSend(lib, page, metasbyyear); + } + + [Route("Suche/Person/{person}")] + public IActionResult Person(string person, int page = 0) { + if (String.IsNullOrWhiteSpace(person)) return _error404(); + var lib = _lib.GetLibrary(); + List>? metasbyyear = null; + var letters = lib.Metas + .Where(x => x.Value.Senders.Contains(person) || x.Value.Receivers.Contains(person)) + .Select(x => x.Value); + if (letters == null) return _error404(); + metasbyyear = letters.ToLookup(x => x.Sort.Year).OrderBy(x => x.Key).ToList(); return _paginateSend(lib, page, metasbyyear, person); } + private List<(string Key, string Person)> _getAvailablePersons(ILibrary lib) { return lib.Persons .OrderBy(x => x.Value.Surname) @@ -106,7 +133,15 @@ public class SucheController : Controller { return res; } - private IActionResult _paginateSend(ILibrary lib, int page, List>? metasbyyear, string? person = null, string? zhvolume = null, string? zhpage = null) { + private IActionResult _paginateSend( + ILibrary lib, + int page, + List> metasbyyear, + string? person = null, + string? zhvolume = null, + string? zhpage = null, + string? activeSearch = null, + Dictionary>? searchResults = null) { var pages = _paginate(metasbyyear); if (pages != null && page >= pages.Count) return _error404(); if (pages == null && page > 0) return _error404(); @@ -123,7 +158,7 @@ public class SucheController : Controller { List<(string Volume, List Pages)>? availablePages = null; availablePages = lib.Structure.Select(x => (x.Key, x.Value.Select(x => x.Key).ToList())).ToList(); zhvolume = zhvolume == null ? "1" : zhvolume; - var model = new SucheViewModel(letters, page, pages, _getAvailablePersons(lib), availablePages.OrderBy(x => x.Volume).ToList(), zhvolume, zhpage); + var model = new SucheViewModel(letters, page, pages, _getAvailablePersons(lib), availablePages.OrderBy(x => x.Volume).ToList(), zhvolume, zhpage, activeSearch, searchResults); if (person != null) model.ActivePerson = person; return View("Index", model); } diff --git a/HaWeb/FileHelpers/HaDocumentWrapper.cs b/HaWeb/FileHelpers/HaDocumentWrapper.cs index 387043f..abc163b 100644 --- a/HaWeb/FileHelpers/HaDocumentWrapper.cs +++ b/HaWeb/FileHelpers/HaDocumentWrapper.cs @@ -2,6 +2,11 @@ namespace HaWeb.FileHelpers; using HaDocument.Interfaces; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.FileProviders; +using System.Collections.Concurrent; +using System.Threading.Tasks; +using HaXMLReader.Interfaces; +using HaWeb.SearchHelpers; +using System.Text; public class HaDocumentWrapper : IHaDocumentWrappper { private ILibrary Library; @@ -10,6 +15,8 @@ public class HaDocumentWrapper : IHaDocumentWrappper { public int StartYear { get; private set; } public int EndYear { get; private set; } + public List? SearchableLetters { get; private set; } + public HaDocumentWrapper(IXMLProvider xmlProvider, IConfiguration configuration) { _xmlProvider = xmlProvider; StartYear = configuration.GetValue("AvailableStartYear"); @@ -41,9 +48,45 @@ public class HaDocumentWrapper : IHaDocumentWrappper { return null; } + var searchableletters = new ConcurrentBag(); + var letters = Library.Letters.Values; + + Parallel.ForEach(letters, letter => { + var o = new SearchHelpers.SeachableItem(letter.Index, _prepareSearch(letter)); + searchableletters.Add(o); + }); + + this.SearchableLetters = searchableletters.ToList(); + return Library; } + public List<(string Index, List<(string Page, string Line, string Preview)> Results)>? SearchLetters(string searchword, IReaderService reader) { + if (SearchableLetters == null) return null; + var res = new ConcurrentBag<(string Index, List<(string Page, string Line, string preview)> Results)>(); + var sw = StringHelpers.NormalizeWhiteSpace(searchword.Trim()); + Parallel.ForEach(SearchableLetters, (letter) => { + var state = new SearchState(sw); + var rd = reader.RequestStringReader(letter.SearchText); + var parser = new HaWeb.HTMLParser.LineXMLHelper(state, rd, new StringBuilder(), null, null, null, SearchRules.TextRules, SearchRules.WhitespaceRules); + rd.Read(); + if (state.Results != null) + res.Add(( + letter.Index, + state.Results.Select(x => ( + x.Page, + x.Line, + parser.Lines != null ? + parser.Lines + .Where(y => y.Page == x.Page && y.Line == x.Line) + .Select(x => x.Text) + .FirstOrDefault(string.Empty) + : "" + )).ToList())); + }); + return res.ToList(); + } + public ILibrary GetLibrary() { return Library; } @@ -57,4 +100,8 @@ public class HaDocumentWrapper : IHaDocumentWrappper { } } } + + private string _prepareSearch(HaDocument.Interfaces.ISearchable objecttoseach) { + return SearchHelpers.StringHelpers.NormalizeWhiteSpace(objecttoseach.Element, ' ', false); + } } \ No newline at end of file diff --git a/HaWeb/FileHelpers/IHaDocumentWrapper.cs b/HaWeb/FileHelpers/IHaDocumentWrapper.cs index f56230f..3d7e563 100644 --- a/HaWeb/FileHelpers/IHaDocumentWrapper.cs +++ b/HaWeb/FileHelpers/IHaDocumentWrapper.cs @@ -1,9 +1,11 @@ namespace HaWeb.FileHelpers; using HaDocument.Interfaces; using Microsoft.AspNetCore.Mvc.ModelBinding; +using HaXMLReader.Interfaces; public interface IHaDocumentWrappper { public ILibrary ResetLibrary(); public ILibrary? SetLibrary(string filepath, ModelStateDictionary ModelState); public ILibrary GetLibrary(); + public List<(string Index, List<(string Page, string Line, string Preview)> Results)>? SearchLetters(string searchword, IReaderService reader); } \ No newline at end of file diff --git a/HaWeb/HTMLHelpers/CommentHelper.cs b/HaWeb/HTMLHelpers/CommentHelper.cs index 240ff5a..e78a3ac 100644 --- a/HaWeb/HTMLHelpers/CommentHelper.cs +++ b/HaWeb/HTMLHelpers/CommentHelper.cs @@ -53,7 +53,7 @@ public static class CommentHelpers { sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT)); } sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT)); - rd = readerService.RequestStringReader(comment.Entry); + rd = readerService.RequestStringReader(comment.Element); new HTMLParser.XMLHelper(commentState, rd, sb, CommentRules.OTagRules, CommentRules.STagRules, CommentRules.CTagRules, CommentRules.TextRules, CommentRules.WhitespaceRules); new HTMLHelpers.LinkHelper(lib, rd, sb); rd.Read(); diff --git a/HaWeb/HTMLParser/LineXMLHelper.cs b/HaWeb/HTMLParser/LineXMLHelper.cs new file mode 100644 index 0000000..c65ba7f --- /dev/null +++ b/HaWeb/HTMLParser/LineXMLHelper.cs @@ -0,0 +1,170 @@ +namespace HaWeb.HTMLParser; +using HaXMLReader.Interfaces; +using HaXMLReader.EvArgs; +using System.Text; +using System.Collections.Generic; +using System; + + +public class LineXMLHelper { + protected IReader _in; + protected StringBuilder _target; + protected List<(Func, bool>, Action>)>? _OTag_Funcs; + protected List<(Func, bool>, Action>)>? _STag_Funcs; + protected List<(Func, bool>, Action>)>? _CTag_Funcs; + protected List<(Func, bool>, Action>)>? _Text_Funcs; + protected List<(Func, bool>, Action>)>? _WS_Funcs; + + public T State; + public Stack OpenTags; + public Dictionary> LastSingleTags; + public StringBuilder LastText; + + public string CurrentPage { get; private set; } + public string CurrentLine { get; private set; } + public List<(string Page, string Line, string Text)>? Lines { get; private set; } + public (string Page, string Line)? CatchPageLine { get; private set; } + + private bool _newpage; + private bool _firstline; + private StringBuilder _currentText; + + public LineXMLHelper( + T state, + IReader input, + StringBuilder target, + List<(Func, bool>, Action>)>? OTag_Funcs = null, + List<(Func, bool>, Action>)>? STag_Funcs = null, + List<(Func, bool>, Action>)>? CTag_Funcs = null, + List<(Func, bool>, Action>)>? Text_Funcs = null, + List<(Func, bool>, Action>)>? WS_Funcs = null, + string startPage = "-1", + string startLine = "-1", + // Set if only a specific line should be appended to the output + (string Page, string Line)? CatchPageLine = null + ) { + + if (input == null || target == null || state == null) throw new ArgumentNullException(); + + State = state; + _in = input; + _target = target; + + _OTag_Funcs = OTag_Funcs; + _STag_Funcs = STag_Funcs; + _CTag_Funcs = CTag_Funcs; + _Text_Funcs = Text_Funcs; + _WS_Funcs = WS_Funcs; + + OpenTags = new Stack(); + LastSingleTags = new Dictionary>(); + LastText = new StringBuilder(); + + CurrentPage = startPage; + CurrentLine = startLine; + _currentText = new StringBuilder(); + input.SingleTag += _setPageLine; + input.ReadingStop += _pushLine; + _newpage = false; + _firstline = true; + + if (_OTag_Funcs != null) + _in.OpenTag += OnOTag; + if (_STag_Funcs != null) + _in.SingleTag += OnSTag; + if (_CTag_Funcs != null) + _in.CloseTag += OnCTag; + if (_Text_Funcs != null) + _in.Text += OnText; + if (_WS_Funcs != null) + _in.Whitespace += OnWS; + } + + private void _pushLine(object? _, EventArgs _empty) { + if (Lines == null) Lines = new List<(string Page, string Line, string Text)>(); + Lines.Add((CurrentPage, CurrentLine, _currentText.ToString())); + _currentText.Clear(); + } + + private void _setPageLine(object? _, Tag tag) { + if (tag.Name == "page" && !String.IsNullOrWhiteSpace(tag["index"])) { + _pushLine(_, EventArgs.Empty); + CurrentPage = tag["index"]; + _newpage = true; + } + if (tag.Name == "line" && !String.IsNullOrWhiteSpace(tag["index"])) { + if (!_newpage && !_firstline) { + _pushLine(_, EventArgs.Empty); + } + _firstline = false; + _newpage = false; + CurrentLine = tag["index"]; + } + } + + protected virtual void OnText(object? _, Text text) { + LastText.Append(text.Value); + if (_Text_Funcs != null) + if (CatchPageLine == null || (CurrentPage == CatchPageLine.Value.Page && CurrentLine == CatchPageLine.Value.Line)) { + _currentText.Append(text.Value); + foreach (var entry in _Text_Funcs) + if (entry.Item1(text, this)) entry.Item2(_target, text, this); + } + } + + protected virtual void OnWS(object? _, Whitespace ws) { + LastText.Append(ws.Value); + if (_WS_Funcs != null) + if (CatchPageLine == null || (CurrentPage == CatchPageLine.Value.Page && CurrentLine == CatchPageLine.Value.Line)) { + _currentText.Append(ws.Value); + foreach (var entry in _WS_Funcs) + if (entry.Item1(ws, this)) entry.Item2(_target, ws, this); + } + } + + protected virtual void OnOTag(object? _, Tag tag) { + OpenTags.Push(tag); + if (_OTag_Funcs != null) + foreach (var entry in _OTag_Funcs) + if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); + } + + + protected virtual void OnSTag(object? _, Tag tag) { + if (!LastSingleTags.ContainsKey(tag.Name)) + LastSingleTags.Add(tag.Name, new List()); + LastSingleTags[tag.Name].Add(tag); + if (_STag_Funcs != null) + foreach (var entry in _STag_Funcs) + if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); + } + + protected virtual void OnCTag(object? _, Tag tag) { + OpenTags.Pop(); + if (_CTag_Funcs != null) + foreach (var entry in _CTag_Funcs) + if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); + } + + internal void Dispose() { + OpenTags.Clear(); + LastSingleTags.Clear(); + LastText.Clear(); + if (_in != null) { + if (_OTag_Funcs != null) + _in.OpenTag -= OnOTag; + if (_STag_Funcs != null) + _in.SingleTag -= OnSTag; + if (_CTag_Funcs != null) + _in.CloseTag -= OnCTag; + if (_Text_Funcs != null) + _in.Text -= OnText; + if (_WS_Funcs != null) + _in.Whitespace -= OnWS; + } + } + + ~LineXMLHelper() { + Dispose(); + } +} \ No newline at end of file diff --git a/HaWeb/HTMLParser/Parser.cs b/HaWeb/HTMLParser/Parser.cs deleted file mode 100644 index 40ac54c..0000000 --- a/HaWeb/HTMLParser/Parser.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace HaWeb.HTMLParser; - -class GenericParser where T : IState { - -} \ No newline at end of file diff --git a/HaWeb/HTMLParser/XMLHelper.cs b/HaWeb/HTMLParser/XMLHelper.cs index 9727064..4170867 100644 --- a/HaWeb/HTMLParser/XMLHelper.cs +++ b/HaWeb/HTMLParser/XMLHelper.cs @@ -7,15 +7,13 @@ using System; public class XMLHelper where T : IState { - private IReader _in; - private StringBuilder _target; - private List<(Func, bool>, Action>)>? _OTag_Funcs; - private List<(Func, bool>, Action>)>? _STag_Funcs; - private List<(Func, bool>, Action>)>? _CTag_Funcs; - private List<(Func, bool>, Action>)>? _Text_Funcs; - private List<(Func, bool>, Action>)>? _WS_Funcs; - private bool _deleteLeadingWS; - private bool _deleteTrailingWS; + protected IReader _in; + protected StringBuilder _target; + protected List<(Func, bool>, Action>)>? _OTag_Funcs; + protected List<(Func, bool>, Action>)>? _STag_Funcs; + protected List<(Func, bool>, Action>)>? _CTag_Funcs; + protected List<(Func, bool>, Action>)>? _Text_Funcs; + protected List<(Func, bool>, Action>)>? _WS_Funcs; public T State; public Stack OpenTags; @@ -30,9 +28,7 @@ public class XMLHelper where T : IState List<(Func, bool>, Action>)>? STag_Funcs = null, List<(Func, bool>, Action>)>? CTag_Funcs = null, List<(Func, bool>, Action>)>? Text_Funcs = null, - List<(Func, bool>, Action>)>? WS_Funcs = null, - bool deleteLeadingWS = false, - bool deleteTrailingWS = false + List<(Func, bool>, Action>)>? WS_Funcs = null ) { if (input == null || target == null || state == null) throw new ArgumentNullException(); @@ -40,8 +36,6 @@ public class XMLHelper where T : IState State = state; _in = input; _target = target; - _deleteLeadingWS = deleteLeadingWS; - _deleteTrailingWS = deleteTrailingWS; _OTag_Funcs = OTag_Funcs; _STag_Funcs = STag_Funcs; @@ -65,7 +59,7 @@ public class XMLHelper where T : IState _in.Whitespace += OnWS; } - void OnOTag(object _, Tag tag) + protected virtual void OnOTag(object? _, Tag tag) { OpenTags.Push(tag); if (_OTag_Funcs != null) @@ -73,17 +67,15 @@ public class XMLHelper where T : IState if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); } - void OnText(object _, Text text) + protected virtual void OnText(object? _, Text text) { - if (_deleteLeadingWS) text.Value = text.Value.TrimStart(); - if (_deleteTrailingWS) text.Value = text.Value.TrimEnd(); LastText.Append(text.Value); if (_Text_Funcs != null) foreach (var entry in _Text_Funcs) if (entry.Item1(text, this)) entry.Item2(_target, text, this); } - void OnSTag(object _, Tag tag) + protected virtual void OnSTag(object? _, Tag tag) { if (!LastSingleTags.ContainsKey(tag.Name)) LastSingleTags.Add(tag.Name, new List()); @@ -93,7 +85,7 @@ public class XMLHelper where T : IState if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); } - void OnCTag(object _, Tag tag) + protected virtual void OnCTag(object? _, Tag tag) { OpenTags.Pop(); if(_CTag_Funcs != null) @@ -101,7 +93,7 @@ public class XMLHelper where T : IState if (entry.Item1(tag, this)) entry.Item2(_target, tag, this); } - void OnWS(object _, Whitespace ws) + protected virtual void OnWS(object? _, Whitespace ws) { LastText.Append(ws.Value); if (_WS_Funcs != null) @@ -111,9 +103,9 @@ public class XMLHelper where T : IState internal void Dispose() { - OpenTags = null; - LastSingleTags = null; - LastText = null; + OpenTags.Clear(); + LastSingleTags.Clear(); + LastText.Clear(); if (_in != null) { if (_OTag_Funcs != null) diff --git a/HaWeb/Models/SearchResult.cs b/HaWeb/Models/SearchResult.cs index 19d2e59..e01c0e5 100644 --- a/HaWeb/Models/SearchResult.cs +++ b/HaWeb/Models/SearchResult.cs @@ -4,31 +4,17 @@ using HaDocument.Comparers; using HaDocument.Interfaces; using System.Collections.Generic; -public class DocumentSearchResult { - public Meta MetaData { get; } - public List Results { get; } +public class SearchResult { + public string Search { get; private set; } + public string Index { get; private set; } + public string? Page { get; set; } + public string? Line { get; set; } + public string? Preview { get; set; } + // TODO: + public string? ParsedPreview { get; set; } - public DocumentSearchResult(Meta meta) { - MetaData = meta; - Results = new List(4); - } -} - -public class DocumentResult { - public string PreviewString { get; } - public string Page { get; } - public string Line { get; } - - public DocumentResult(string previewstring, string page, string line) { - PreviewString = previewstring; - Page = page; - Line = line; - } -} - -public class LetterComparer : IComparer { - public int Compare(DocumentSearchResult first, DocumentSearchResult second) { - var cmp = new DefaultComparer(); - return cmp.Compare(first.MetaData, second.MetaData); + public SearchResult(string search, string index) { + Search = search; + Index = index; } } \ No newline at end of file diff --git a/HaWeb/Models/SucheViewModel.cs b/HaWeb/Models/SucheViewModel.cs index fcd443c..8cfbd07 100644 --- a/HaWeb/Models/SucheViewModel.cs +++ b/HaWeb/Models/SucheViewModel.cs @@ -1,4 +1,5 @@ namespace HaWeb.Models; +using HaDocument.Models; public class SucheViewModel { public List<(int Year, List LetterList)>? Letters { get; private set; } @@ -11,6 +12,7 @@ public class SucheViewModel { public string? ActiveVolume { get; private set; } public string? ActivePage { get; private set; } public string? ActiveSearch { get; private set; } + public Dictionary>? SearchResults { get; private set; } public SucheViewModel( List<(int Year, List LetterList)>? letters, @@ -19,7 +21,9 @@ public class SucheViewModel { List<(string Key, string Name)>? availablePersons, List<(string Volume, List Pages)>? availablePages, string? activeVolume, - string? activePage + string? activePage, + string? activeSearch, + Dictionary>? searchResults ) { Letters = letters; if (letters != null) @@ -32,5 +36,7 @@ public class SucheViewModel { AvailablePages = availablePages; ActiveVolume = activeVolume; ActivePage = activePage; + ActiveSearch = activeSearch; + SearchResults = searchResults; } } \ No newline at end of file diff --git a/HaWeb/README.md b/HaWeb/README.md index 37b1f8e..e54aa92 100644 --- a/HaWeb/README.md +++ b/HaWeb/README.md @@ -73,4 +73,6 @@ TODO Abhärten des Konstruktors von XMLRootDokument für von außerhalb platzier TODO XML-Check im Client TODO Lock für die Liste, Bzw ConcurretBag TODO 516A david friedlaender in den traditions -TODO 3 Zeilen marginal schließt perfekt an 2 zeilen text an \ No newline at end of file +TODO 3 Zeilen marginal schließt perfekt an 2 zeilen text an + +TODO Einfügungszeichen zerstört suchergebnisse \ No newline at end of file diff --git a/HaWeb/Search.cs b/HaWeb/Search.cs index e7f379e..9bbefbb 100644 --- a/HaWeb/Search.cs +++ b/HaWeb/Search.cs @@ -1,76 +1,76 @@ -namespace HaWeb; -using HaDocument.Models; -using HaDocument.Interfaces; -using HaDocument.Comparers; -using HaXMLReader.Interfaces; -using HaWeb.Models; -using HaXMLReader.EvArgs; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Threading.Tasks; -using System.Linq; -using System; +// namespace HaWeb; +// using HaDocument.Models; +// using HaDocument.Interfaces; +// using HaDocument.Comparers; +// using HaXMLReader.Interfaces; +// using HaWeb.Models; +// using HaXMLReader.EvArgs; +// using System.Collections.Concurrent; +// using System.Collections.Generic; +// using System.Collections.Immutable; +// using System.Threading.Tasks; +// using System.Linq; +// using System; -internal class DocumentSearch { - internal DocumentSearchResult _searchResult { get; } - private IReader _reader { get; } - private string _search { get; } +// internal class DocumentSearch { +// internal DocumentSearchResult _searchResult { get; } +// private IReader _reader { get; } +// private string _search { get; } - internal DocumentSearch(DocumentSearchResult res, IReader reader, string search) { - _searchResult = res; - _reader = reader; - _search = search; - _pg = ""; - } +// internal DocumentSearch(DocumentSearchResult res, IReader reader, string search) { +// _searchResult = res; +// _reader = reader; +// _search = search; +// _pg = ""; +// } - internal DocumentSearchResult Act() { - _reader.Text += OnText; - _reader.SingleTag += OnSTag; - _reader.Read(); - return _searchResult; - } +// internal DocumentSearchResult Act() { +// _reader.Text += OnText; +// _reader.SingleTag += OnSTag; +// _reader.Read(); +// return _searchResult; +// } - internal void OnText(object _, Text text) { - if (text.Value.ToLower().Contains(_search.ToLower())) { - _searchResult.Results.Add(new DocumentResult(text.Value, _pg, _ln)); - } - } +// internal void OnText(object _, Text text) { +// if (text.Value.ToLower().Contains(_search.ToLower())) { +// _searchResult.Results.Add(new DocumentResult(text.Value, _pg, _ln)); +// } +// } - private string _pg; - private string _ln; +// private string _pg; +// private string _ln; - internal void OnSTag(object _, Tag tag) { - if (tag.Name == "page") - _pg = tag["index"]; - else if (tag.Name == "line") - _ln = tag["index"]; - } -} +// internal void OnSTag(object _, Tag tag) { +// if (tag.Name == "page") +// _pg = tag["index"]; +// else if (tag.Name == "line") +// _ln = tag["index"]; +// } +// } -internal class RegisterSearch { - internal Comment _searchResult { get; } - private IReader _reader { get; } - private string _search { get; } - private bool found; +// internal class RegisterSearch { +// internal Comment _searchResult { get; } +// private IReader _reader { get; } +// private string _search { get; } +// private bool found; - internal RegisterSearch(Comment res, IReader reader, string search) { - _searchResult = res; - _reader = reader; - _search = search; - found = false; - } +// internal RegisterSearch(Comment res, IReader reader, string search) { +// _searchResult = res; +// _reader = reader; +// _search = search; +// found = false; +// } - internal bool Act() { - _reader.Text += OnText; - _reader.Read(); - return found; - } +// internal bool Act() { +// _reader.Text += OnText; +// _reader.Read(); +// return found; +// } - internal void OnText(object _, Text text) { - if (text.Value.ToLower().Contains(_search.ToLower())) { - found = true; - } - } -} +// internal void OnText(object _, Text text) { +// if (text.Value.ToLower().Contains(_search.ToLower())) { +// found = true; +// } +// } +// } diff --git a/HaWeb/SearchHelpers/ISearchable.cs b/HaWeb/SearchHelpers/ISearchable.cs new file mode 100644 index 0000000..5a58e92 --- /dev/null +++ b/HaWeb/SearchHelpers/ISearchable.cs @@ -0,0 +1,6 @@ +namespace HaWeb.SearchHelpers; + +public interface ISearchable { + public string Index { get; } + public string SearchText { get; } +} \ No newline at end of file diff --git a/HaWeb/SearchHelpers/SearchRules.cs b/HaWeb/SearchHelpers/SearchRules.cs new file mode 100644 index 0000000..d5e095c --- /dev/null +++ b/HaWeb/SearchHelpers/SearchRules.cs @@ -0,0 +1,46 @@ +namespace HaWeb.SearchHelpers; +using System.Text; +using System.Web; + +using TagFuncList = List<(Func, bool>, Action>)>; +using TextFuncList = List<(Func, bool>, Action>)>; +using WhitespaceFuncList = List<(Func, bool>, Action>)>; + + +public class SearchRules { + public static readonly TextFuncList TextRules = new TextFuncList() { + ( (x, _) => true, (sb, text, reader) => { + var t = text.Value; + if (reader.State.Normalize) + t = HaWeb.SearchHelpers.StringHelpers.NormalizeWhiteSpace(t); + sb.Append(t); + var sw = reader.State.SearchWord; + if (sb.Length >= sw.Length) { + if (sb.ToString().ToLower().Contains(sw)) { + if (reader.State.Results == null) + reader.State.Results = new List<(string Page, string Line)>(); + reader.State.Results.Add((reader.CurrentPage, reader.CurrentLine)); + } + sb.Remove(0, sb.Length - sw.Length); + } + }) + }; + + public static readonly WhitespaceFuncList WhitespaceRules= new WhitespaceFuncList() { + ( (x, _) => true, (sb, text, reader) => { + var t = text.Value; + if (reader.State.Normalize) + t = HaWeb.SearchHelpers.StringHelpers.NormalizeWhiteSpace(t); + sb.Append(t); + var sw = reader.State.SearchWord; + if (sb.Length >= sw.Length) { + if (sb.ToString().Contains(sw)) { + if (reader.State.Results == null) + reader.State.Results = new List<(string Page, string Line)>(); + reader.State.Results.Add((reader.CurrentPage, reader.CurrentLine)); + } + sb.Remove(0, sb.Length - sw.Length); + } + }) + }; +} \ No newline at end of file diff --git a/HaWeb/SearchHelpers/SearchState.cs b/HaWeb/SearchHelpers/SearchState.cs new file mode 100644 index 0000000..e19f2a6 --- /dev/null +++ b/HaWeb/SearchHelpers/SearchState.cs @@ -0,0 +1,15 @@ +namespace HaWeb.SearchHelpers; +using System.Text; + +public class SearchState : HaWeb.HTMLParser.IState { + internal string SearchWord; + internal bool Normalize; + internal List<(string Page, string Line)>? Results; + + public SearchState(string searchword, bool normalize = false) { + Normalize = normalize; + SearchWord = searchword; + } + + public void SetupState() {} +} \ No newline at end of file diff --git a/HaWeb/SearchHelpers/SearchableItem.cs b/HaWeb/SearchHelpers/SearchableItem.cs new file mode 100644 index 0000000..374de69 --- /dev/null +++ b/HaWeb/SearchHelpers/SearchableItem.cs @@ -0,0 +1,11 @@ +namespace HaWeb.SearchHelpers; + +public class SeachableItem : ISearchable { + public string Index { get; private set; } + public string SearchText { get; private set; } + + public SeachableItem(string index, string searchtext) { + this.Index = index; + this.SearchText = searchtext; + } +} \ No newline at end of file diff --git a/HaWeb/SearchHelpers/StringHelpers.cs b/HaWeb/SearchHelpers/StringHelpers.cs new file mode 100644 index 0000000..01979b1 --- /dev/null +++ b/HaWeb/SearchHelpers/StringHelpers.cs @@ -0,0 +1,39 @@ +namespace HaWeb.SearchHelpers; +using System.Text; + +public static class StringHelpers { + public static string NormalizeWhiteSpace(string input, char normalizeTo = ' ', bool toLower = true) { + if (string.IsNullOrEmpty(input)) { + return string.Empty; + } + + StringBuilder output = new StringBuilder(); + + // TODO: what about punctuation (char.IsPunctuation()) ? what about spaces? + + // Remove all whitespace, search becomes whitespace insensitive + // foreach (var c in input) + // if (!char.IsWhiteSpace(c)) { + // if (toLower) output.Append(char.ToLower(c)); + // else output.Append(c); + // } + + // Collapse all whitespace into a single whitespace: + bool skipped = false; + + foreach (char c in input) { + if (char.IsWhiteSpace(c)) { + if (!skipped) { + output.Append(normalizeTo); + skipped = true; + } + } else { + skipped = false; + if (toLower) output.Append(char.ToLower(c)); + else output.Append(c); + } + } + + return output.ToString(); + } +} \ No newline at end of file diff --git a/HaWeb/Views/Suche/Index.cshtml b/HaWeb/Views/Suche/Index.cshtml index 972dc01..8b9679b 100644 --- a/HaWeb/Views/Suche/Index.cshtml +++ b/HaWeb/Views/Suche/Index.cshtml @@ -14,7 +14,7 @@
@if (Model.AvailableYears != null && Model.AvailableYears.Any()) { @for(var i = 0; i < Model.AvailableYears.Count; i++) { - + @if (Model.AvailableYears[i].StartYear != Model.AvailableYears[i].EndYear) { @Model.AvailableYears[i].StartYear-@Model.AvailableYears[i].EndYear @@ -41,6 +41,12 @@ @await Html.PartialAsync("/Views/Shared/_LetterHead.cshtml", (letter, true)) + @if (Model.SearchResults != null && Model.SearchResults.ContainsKey(letter.Meta.Index)) { + @foreach (var item in Model.SearchResults[letter.Meta.Index]) + { +

@item.Page / @item.Line: @item.Preview

+ } + } } }
@@ -148,7 +154,7 @@ }
- +
@@ -164,7 +170,7 @@ const SUBMITSEARCH = function(filter) { let f = filter.value; - window.location.href = "/Suche/" + f; + window.location.href = "/Suche?search=" + f; } window.addEventListener("load", () => { @@ -192,7 +198,7 @@
Alle @foreach (var person in Model.AvailablePersons) { - + @person.Name }