mirror of
https://github.com/Theodor-Springmann-Stiftung/hamann-ausgabe-core.git
synced 2025-10-29 17:25:32 +00:00
Implemented Fulltext search across tags and linebreaks for letters
This commit is contained in:
6
HaDocumentV6/Interfaces/ISearchable.cs
Normal file
6
HaDocumentV6/Interfaces/ISearchable.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace HaDocument.Interfaces;
|
||||
|
||||
public interface ISearchable {
|
||||
public string Element { get; }
|
||||
public string Index { get; }
|
||||
}
|
||||
@@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace HaDocument.Models{
|
||||
public class Comment{
|
||||
public string Entry { get; } = "";
|
||||
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<string, Comment> subComments,
|
||||
string parent=""
|
||||
) {
|
||||
Entry = entry;
|
||||
Element = entry;
|
||||
Index = index;
|
||||
Type = type;
|
||||
Lemma = lemma;
|
||||
|
||||
@@ -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; } = "";
|
||||
|
||||
@@ -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; } = "";
|
||||
|
||||
|
||||
@@ -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; } = "";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace HaDocument.Models {
|
||||
public class Tradition {
|
||||
public class Tradition : HaDocument.Interfaces.ISearchable {
|
||||
public string Index { get; } = "";
|
||||
public string Element { get; } = "";
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<int>("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<IGrouping<int, Meta>>? metasbyyear = null;
|
||||
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<IGrouping<int, Meta>>? 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();
|
||||
}
|
||||
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<IGrouping<int, Meta>>? metasbyyear, string? person = null, string? zhvolume = null, string? zhpage = null) {
|
||||
private IActionResult _paginateSend(
|
||||
ILibrary lib,
|
||||
int page,
|
||||
List<IGrouping<int, Meta>> metasbyyear,
|
||||
string? person = null,
|
||||
string? zhvolume = null,
|
||||
string? zhpage = null,
|
||||
string? activeSearch = null,
|
||||
Dictionary<string, List<SearchResult>>? 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<string> 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);
|
||||
}
|
||||
|
||||
@@ -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<SearchHelpers.SeachableItem>? SearchableLetters { get; private set; }
|
||||
|
||||
public HaDocumentWrapper(IXMLProvider xmlProvider, IConfiguration configuration) {
|
||||
_xmlProvider = xmlProvider;
|
||||
StartYear = configuration.GetValue<int>("AvailableStartYear");
|
||||
@@ -41,9 +48,45 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
|
||||
return null;
|
||||
}
|
||||
|
||||
var searchableletters = new ConcurrentBag<SearchHelpers.SeachableItem>();
|
||||
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<SearchState>(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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>(commentState, rd, sb, CommentRules.OTagRules, CommentRules.STagRules, CommentRules.CTagRules, CommentRules.TextRules, CommentRules.WhitespaceRules);
|
||||
new HTMLHelpers.LinkHelper(lib, rd, sb);
|
||||
rd.Read();
|
||||
|
||||
170
HaWeb/HTMLParser/LineXMLHelper.cs
Normal file
170
HaWeb/HTMLParser/LineXMLHelper.cs
Normal file
@@ -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<T> {
|
||||
protected IReader _in;
|
||||
protected StringBuilder _target;
|
||||
protected List<(Func<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? _OTag_Funcs;
|
||||
protected List<(Func<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? _STag_Funcs;
|
||||
protected List<(Func<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? _CTag_Funcs;
|
||||
protected List<(Func<Text, LineXMLHelper<T>, bool>, Action<StringBuilder, Text, LineXMLHelper<T>>)>? _Text_Funcs;
|
||||
protected List<(Func<Whitespace, LineXMLHelper<T>, bool>, Action<StringBuilder, Whitespace, LineXMLHelper<T>>)>? _WS_Funcs;
|
||||
|
||||
public T State;
|
||||
public Stack<Tag> OpenTags;
|
||||
public Dictionary<string, List<Tag>> 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<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? OTag_Funcs = null,
|
||||
List<(Func<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? STag_Funcs = null,
|
||||
List<(Func<Tag, LineXMLHelper<T>, bool>, Action<StringBuilder, Tag, LineXMLHelper<T>>)>? CTag_Funcs = null,
|
||||
List<(Func<Text, LineXMLHelper<T>, bool>, Action<StringBuilder, Text, LineXMLHelper<T>>)>? Text_Funcs = null,
|
||||
List<(Func<Whitespace, LineXMLHelper<T>, bool>, Action<StringBuilder, Whitespace, LineXMLHelper<T>>)>? 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<Tag>();
|
||||
LastSingleTags = new Dictionary<string, List<Tag>>();
|
||||
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<Tag>());
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
namespace HaWeb.HTMLParser;
|
||||
|
||||
class GenericParser<T> where T : IState {
|
||||
|
||||
}
|
||||
@@ -7,15 +7,13 @@ using System;
|
||||
|
||||
public class XMLHelper<T> where T : IState
|
||||
{
|
||||
private IReader _in;
|
||||
private StringBuilder _target;
|
||||
private List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _OTag_Funcs;
|
||||
private List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _STag_Funcs;
|
||||
private List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _CTag_Funcs;
|
||||
private List<(Func<Text, XMLHelper<T>, bool>, Action<StringBuilder, Text, XMLHelper<T>>)>? _Text_Funcs;
|
||||
private List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? _WS_Funcs;
|
||||
private bool _deleteLeadingWS;
|
||||
private bool _deleteTrailingWS;
|
||||
protected IReader _in;
|
||||
protected StringBuilder _target;
|
||||
protected List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _OTag_Funcs;
|
||||
protected List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _STag_Funcs;
|
||||
protected List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _CTag_Funcs;
|
||||
protected List<(Func<Text, XMLHelper<T>, bool>, Action<StringBuilder, Text, XMLHelper<T>>)>? _Text_Funcs;
|
||||
protected List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? _WS_Funcs;
|
||||
|
||||
public T State;
|
||||
public Stack<Tag> OpenTags;
|
||||
@@ -30,9 +28,7 @@ public class XMLHelper<T> where T : IState
|
||||
List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? STag_Funcs = null,
|
||||
List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? CTag_Funcs = null,
|
||||
List<(Func<Text, XMLHelper<T>, bool>, Action<StringBuilder, Text, XMLHelper<T>>)>? Text_Funcs = null,
|
||||
List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? WS_Funcs = null,
|
||||
bool deleteLeadingWS = false,
|
||||
bool deleteTrailingWS = false
|
||||
List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? WS_Funcs = null
|
||||
)
|
||||
{
|
||||
if (input == null || target == null || state == null) throw new ArgumentNullException();
|
||||
@@ -40,8 +36,6 @@ public class XMLHelper<T> 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<T> 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<T> 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<Tag>());
|
||||
@@ -93,7 +85,7 @@ public class XMLHelper<T> 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<T> 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<T> 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)
|
||||
|
||||
@@ -4,31 +4,17 @@ using HaDocument.Comparers;
|
||||
using HaDocument.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class DocumentSearchResult {
|
||||
public Meta MetaData { get; }
|
||||
public List<DocumentResult> 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<DocumentResult>(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<DocumentSearchResult> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace HaWeb.Models;
|
||||
using HaDocument.Models;
|
||||
|
||||
public class SucheViewModel {
|
||||
public List<(int Year, List<BriefeMetaViewModel> 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<string, List<SearchResult>>? SearchResults { get; private set; }
|
||||
|
||||
public SucheViewModel(
|
||||
List<(int Year, List<BriefeMetaViewModel> LetterList)>? letters,
|
||||
@@ -19,7 +21,9 @@ public class SucheViewModel {
|
||||
List<(string Key, string Name)>? availablePersons,
|
||||
List<(string Volume, List<string> Pages)>? availablePages,
|
||||
string? activeVolume,
|
||||
string? activePage
|
||||
string? activePage,
|
||||
string? activeSearch,
|
||||
Dictionary<string, List<SearchResult>>? searchResults
|
||||
) {
|
||||
Letters = letters;
|
||||
if (letters != null)
|
||||
@@ -32,5 +36,7 @@ public class SucheViewModel {
|
||||
AvailablePages = availablePages;
|
||||
ActiveVolume = activeVolume;
|
||||
ActivePage = activePage;
|
||||
ActiveSearch = activeSearch;
|
||||
SearchResults = searchResults;
|
||||
}
|
||||
}
|
||||
@@ -74,3 +74,5 @@ 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
|
||||
|
||||
TODO Einfügungszeichen zerstört suchergebnisse
|
||||
130
HaWeb/Search.cs
130
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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
6
HaWeb/SearchHelpers/ISearchable.cs
Normal file
6
HaWeb/SearchHelpers/ISearchable.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace HaWeb.SearchHelpers;
|
||||
|
||||
public interface ISearchable {
|
||||
public string Index { get; }
|
||||
public string SearchText { get; }
|
||||
}
|
||||
46
HaWeb/SearchHelpers/SearchRules.cs
Normal file
46
HaWeb/SearchHelpers/SearchRules.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
namespace HaWeb.SearchHelpers;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
using TagFuncList = List<(Func<HaXMLReader.EvArgs.Tag, HaWeb.HTMLParser.LineXMLHelper<SearchState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Tag, HaWeb.HTMLParser.LineXMLHelper<SearchState>>)>;
|
||||
using TextFuncList = List<(Func<HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.LineXMLHelper<SearchState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Text, HaWeb.HTMLParser.LineXMLHelper<SearchState>>)>;
|
||||
using WhitespaceFuncList = List<(Func<HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.LineXMLHelper<SearchState>, bool>, Action<System.Text.StringBuilder, HaXMLReader.EvArgs.Whitespace, HaWeb.HTMLParser.LineXMLHelper<SearchState>>)>;
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
15
HaWeb/SearchHelpers/SearchState.cs
Normal file
15
HaWeb/SearchHelpers/SearchState.cs
Normal file
@@ -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() {}
|
||||
}
|
||||
11
HaWeb/SearchHelpers/SearchableItem.cs
Normal file
11
HaWeb/SearchHelpers/SearchableItem.cs
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
39
HaWeb/SearchHelpers/StringHelpers.cs
Normal file
39
HaWeb/SearchHelpers/StringHelpers.cs
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
<div class="ha-searchnav">
|
||||
@if (Model.AvailableYears != null && Model.AvailableYears.Any()) {
|
||||
@for(var i = 0; i < Model.AvailableYears.Count; i++) {
|
||||
<a class="@(Model.ActiveYear == i ? "active" : "")" asp-route-person="@Model.ActivePerson" asp-controller="Suche" asp-action="Index" asp-route-page="@i">
|
||||
<a class="@(Model.ActiveYear == i ? "active" : "")" asp-route-person="@Model.ActivePerson" asp-route-search="@Model.ActiveSearch" asp-controller="Suche" asp-route-page="@i">
|
||||
@if (Model.AvailableYears[i].StartYear != Model.AvailableYears[i].EndYear) {
|
||||
<span>
|
||||
@Model.AvailableYears[i].StartYear-@Model.AvailableYears[i].EndYear
|
||||
@@ -41,6 +41,12 @@
|
||||
<a class="ha-letterlistentry" asp-controller="Briefe" asp-action="Index" asp-route-id="@letter.Meta.Autopsic">
|
||||
@await Html.PartialAsync("/Views/Shared/_LetterHead.cshtml", (letter, true))
|
||||
</a>
|
||||
@if (Model.SearchResults != null && Model.SearchResults.ContainsKey(letter.Meta.Index)) {
|
||||
@foreach (var item in Model.SearchResults[letter.Meta.Index])
|
||||
{
|
||||
<p>@item.Page / @item.Line: @item.Preview</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -148,7 +154,7 @@
|
||||
}
|
||||
</div>
|
||||
<form class="ha-searchform" id="ha-searchform">
|
||||
<input id="ha-searchformtext" type="text" placeholder="Suchbegriff"/>
|
||||
<input id="ha-searchformtext" type="text" placeholder="Suchbegriff" value="@Model.ActiveSearch"/>
|
||||
<button id="ha-searchformsubmit" type="submit">Suchen</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -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 @@
|
||||
<div class="ha-personlist">
|
||||
<a class="ha-personlistperson @(Model.ActivePerson == null ? "active" : "")" asp-controller="Suche" asp-action="Index">Alle</a>
|
||||
@foreach (var person in Model.AvailablePersons) {
|
||||
<a class="ha-personlistperson @(Model.ActivePerson == person.Key ? "active" : "")" asp-controller="Suche" asp-action="Index" asp-route-person="@person.Key" asp-route-page="@null">
|
||||
<a class="ha-personlistperson @(Model.ActivePerson == person.Key ? "active" : "")" asp-controller="Suche" asp-action="Person" asp-route-person="@person.Key" asp-route-page="@null">
|
||||
@person.Name
|
||||
</a>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user