Implemented Fulltext search across tags and linebreaks for letters

This commit is contained in:
schnulller
2022-06-19 00:01:14 +02:00
parent 078c4b75b8
commit 4ef0e260e0
24 changed files with 510 additions and 148 deletions

View File

@@ -0,0 +1,6 @@
namespace HaDocument.Interfaces;
public interface ISearchable {
public string Element { get; }
public string Index { get; }
}

View File

@@ -2,9 +2,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
namespace HaDocument.Models { namespace HaDocument.Models{
public class Comment{ public class Comment : HaDocument.Interfaces.ISearchable {
public string Entry { get; } = ""; public string Element { get; } = "";
public string Index { get; } = ""; public string Index { get; } = "";
public string Type { get; } = ""; public string Type { get; } = "";
public string Lemma { get; } = ""; public string Lemma { get; } = "";
@@ -21,7 +21,7 @@ namespace HaDocument.Models {
SortedDictionary<string, Comment> subComments, SortedDictionary<string, Comment> subComments,
string parent="" string parent=""
) { ) {
Entry = entry; Element = entry;
Index = index; Index = index;
Type = type; Type = type;
Lemma = lemma; Lemma = lemma;

View File

@@ -1,5 +1,5 @@
namespace HaDocument.Models { namespace HaDocument.Models {
public class Editreason { public class Editreason : HaDocument.Interfaces.ISearchable {
public string Index { get; } = ""; public string Index { get; } = "";
public string Element { get; } = ""; public string Element { get; } = "";
public string Letter { get; } = ""; public string Letter { get; } = "";

View File

@@ -1,5 +1,5 @@
namespace HaDocument.Models { namespace HaDocument.Models {
public class Letter : HaModel { public class Letter : HaModel, HaDocument.Interfaces.ISearchable {
public string Index { get; } = ""; public string Index { get; } = "";
public string Element { get; } = ""; public string Element { get; } = "";

View File

@@ -1,5 +1,5 @@
namespace HaDocument.Models { namespace HaDocument.Models {
public class Marginal { public class Marginal : HaDocument.Interfaces.ISearchable {
public string Index { get; } = ""; public string Index { get; } = "";
public string Letter { get; } = ""; public string Letter { get; } = "";
public string Page { get; } = ""; public string Page { get; } = "";

View File

@@ -1,5 +1,5 @@
namespace HaDocument.Models { namespace HaDocument.Models {
public class Tradition { public class Tradition : HaDocument.Interfaces.ISearchable {
public string Index { get; } = ""; public string Index { get; } = "";
public string Element { get; } = ""; public string Element { get; } = "";

View File

@@ -156,8 +156,6 @@ namespace HaDocument.Reactors {
(_availableVolumes == null && _availableYearRange.Item1 == 0 && _availableYearRange.Item2 == 0) (_availableVolumes == null && _availableYearRange.Item1 == 0 && _availableYearRange.Item2 == 0)
) { ) {
var ZHInfo = !inZH ? null : new ZHInfo(AltLineNumbering, dateChanged, Volume, Page); var ZHInfo = !inZH ? null : new ZHInfo(AltLineNumbering, dateChanged, Volume, Page);
if (Autopsic == "0")
System.Diagnostics.Debugger.Break();
var meta = new Meta( var meta = new Meta(
Index, Index,
Autopsic, Autopsic,

View File

@@ -4,16 +4,19 @@ using HaWeb.Models;
using HaWeb.FileHelpers; using HaWeb.FileHelpers;
using HaDocument.Interfaces; using HaDocument.Interfaces;
using HaDocument.Models; using HaDocument.Models;
using HaXMLReader.Interfaces;
using System.Collections.Specialized; using System.Collections.Specialized;
namespace HaWeb.Controllers; namespace HaWeb.Controllers;
public class SucheController : Controller { public class SucheController : Controller {
private IHaDocumentWrappper _lib; private IHaDocumentWrappper _lib;
private IReaderService _readerService;
private int _lettersForPage; private int _lettersForPage;
public SucheController(IHaDocumentWrappper lib, IConfiguration config) { public SucheController(IHaDocumentWrappper lib, IReaderService readerService, IConfiguration config) {
_lib = lib; _lib = lib;
_readerService = readerService;
_lettersForPage = config.GetValue<int>("LettersOnPage"); _lettersForPage = config.GetValue<int>("LettersOnPage");
} }
@@ -48,21 +51,45 @@ public class SucheController : Controller {
[Route("Suche")] [Route("Suche")]
// Order of actions: // Order of actions:
// Filter, sort by year, paginate, sort by Meta.Sort & .Order, parse // 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(); var lib = _lib.GetLibrary();
List<IGrouping<int, Meta>>? metasbyyear = null; List<IGrouping<int, Meta>>? metasbyyear = null;
if (person != null) { if (search != null) {
var letters = lib.Metas var sw = new System.Diagnostics.Stopwatch();
.Where(x => x.Value.Senders.Contains(person) || x.Value.Receivers.Contains(person)) sw.Start();
.Select(x => x.Value); var res = _lib.SearchLetters(search, _readerService);
if (letters == null) return _error404(); if (res == null || !res.Any()) return _error404();
metasbyyear = letters.ToLookup(x => x.Sort.Year).OrderBy(x => x.Key).ToList(); var ret = res.ToDictionary(
} else { x => x.Index,
metasbyyear = lib.MetasByYear.OrderBy(x => x.Key).ToList(); 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;
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); return _paginateSend(lib, page, metasbyyear, person);
} }
private List<(string Key, string Person)> _getAvailablePersons(ILibrary lib) { private List<(string Key, string Person)> _getAvailablePersons(ILibrary lib) {
return lib.Persons return lib.Persons
.OrderBy(x => x.Value.Surname) .OrderBy(x => x.Value.Surname)
@@ -106,7 +133,15 @@ public class SucheController : Controller {
return res; 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); var pages = _paginate(metasbyyear);
if (pages != null && page >= pages.Count) return _error404(); if (pages != null && page >= pages.Count) return _error404();
if (pages == null && page > 0) 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; List<(string Volume, List<string> Pages)>? availablePages = null;
availablePages = lib.Structure.Select(x => (x.Key, x.Value.Select(x => x.Key).ToList())).ToList(); availablePages = lib.Structure.Select(x => (x.Key, x.Value.Select(x => x.Key).ToList())).ToList();
zhvolume = zhvolume == null ? "1" : zhvolume; 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; if (person != null) model.ActivePerson = person;
return View("Index", model); return View("Index", model);
} }

View File

@@ -2,6 +2,11 @@ namespace HaWeb.FileHelpers;
using HaDocument.Interfaces; using HaDocument.Interfaces;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.FileProviders; 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 { public class HaDocumentWrapper : IHaDocumentWrappper {
private ILibrary Library; private ILibrary Library;
@@ -10,6 +15,8 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
public int StartYear { get; private set; } public int StartYear { get; private set; }
public int EndYear { get; private set; } public int EndYear { get; private set; }
public List<SearchHelpers.SeachableItem>? SearchableLetters { get; private set; }
public HaDocumentWrapper(IXMLProvider xmlProvider, IConfiguration configuration) { public HaDocumentWrapper(IXMLProvider xmlProvider, IConfiguration configuration) {
_xmlProvider = xmlProvider; _xmlProvider = xmlProvider;
StartYear = configuration.GetValue<int>("AvailableStartYear"); StartYear = configuration.GetValue<int>("AvailableStartYear");
@@ -41,9 +48,45 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
return null; 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; 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() { public ILibrary GetLibrary() {
return Library; return Library;
} }
@@ -57,4 +100,8 @@ public class HaDocumentWrapper : IHaDocumentWrappper {
} }
} }
} }
private string _prepareSearch(HaDocument.Interfaces.ISearchable objecttoseach) {
return SearchHelpers.StringHelpers.NormalizeWhiteSpace(objecttoseach.Element, ' ', false);
}
} }

View File

@@ -1,9 +1,11 @@
namespace HaWeb.FileHelpers; namespace HaWeb.FileHelpers;
using HaDocument.Interfaces; using HaDocument.Interfaces;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using HaXMLReader.Interfaces;
public interface IHaDocumentWrappper { public interface IHaDocumentWrappper {
public ILibrary ResetLibrary(); public ILibrary ResetLibrary();
public ILibrary? SetLibrary(string filepath, ModelStateDictionary ModelState); public ILibrary? SetLibrary(string filepath, ModelStateDictionary ModelState);
public ILibrary GetLibrary(); public ILibrary GetLibrary();
public List<(string Index, List<(string Page, string Line, string Preview)> Results)>? SearchLetters(string searchword, IReaderService reader);
} }

View File

@@ -53,7 +53,7 @@ public static class CommentHelpers {
sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT)); sb.Append(HTMLHelpers.TagHelpers.CreateEndElement(DEFAULTELEMENT));
} }
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 HTMLParser.XMLHelper<CommentState>(commentState, rd, sb, CommentRules.OTagRules, CommentRules.STagRules, CommentRules.CTagRules, CommentRules.TextRules, CommentRules.WhitespaceRules);
new HTMLHelpers.LinkHelper(lib, rd, sb); new HTMLHelpers.LinkHelper(lib, rd, sb);
rd.Read(); rd.Read();

View 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();
}
}

View File

@@ -1,5 +0,0 @@
namespace HaWeb.HTMLParser;
class GenericParser<T> where T : IState {
}

View File

@@ -7,15 +7,13 @@ using System;
public class XMLHelper<T> where T : IState public class XMLHelper<T> where T : IState
{ {
private IReader _in; protected IReader _in;
private StringBuilder _target; protected StringBuilder _target;
private 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>>)>? _OTag_Funcs;
private 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>>)>? _STag_Funcs;
private List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? _CTag_Funcs; protected 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; protected 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; protected List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? _WS_Funcs;
private bool _deleteLeadingWS;
private bool _deleteTrailingWS;
public T State; public T State;
public Stack<Tag> OpenTags; 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>>)>? STag_Funcs = null,
List<(Func<Tag, XMLHelper<T>, bool>, Action<StringBuilder, Tag, XMLHelper<T>>)>? CTag_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<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, List<(Func<Whitespace, XMLHelper<T>, bool>, Action<StringBuilder, Whitespace, XMLHelper<T>>)>? WS_Funcs = null
bool deleteLeadingWS = false,
bool deleteTrailingWS = false
) )
{ {
if (input == null || target == null || state == null) throw new ArgumentNullException(); if (input == null || target == null || state == null) throw new ArgumentNullException();
@@ -40,8 +36,6 @@ public class XMLHelper<T> where T : IState
State = state; State = state;
_in = input; _in = input;
_target = target; _target = target;
_deleteLeadingWS = deleteLeadingWS;
_deleteTrailingWS = deleteTrailingWS;
_OTag_Funcs = OTag_Funcs; _OTag_Funcs = OTag_Funcs;
_STag_Funcs = STag_Funcs; _STag_Funcs = STag_Funcs;
@@ -65,7 +59,7 @@ public class XMLHelper<T> where T : IState
_in.Whitespace += OnWS; _in.Whitespace += OnWS;
} }
void OnOTag(object _, Tag tag) protected virtual void OnOTag(object? _, Tag tag)
{ {
OpenTags.Push(tag); OpenTags.Push(tag);
if (_OTag_Funcs != null) 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); 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); LastText.Append(text.Value);
if (_Text_Funcs != null) if (_Text_Funcs != null)
foreach (var entry in _Text_Funcs) foreach (var entry in _Text_Funcs)
if (entry.Item1(text, this)) entry.Item2(_target, text, this); 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)) if (!LastSingleTags.ContainsKey(tag.Name))
LastSingleTags.Add(tag.Name, new List<Tag>()); 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); if (entry.Item1(tag, this)) entry.Item2(_target, tag, this);
} }
void OnCTag(object _, Tag tag) protected virtual void OnCTag(object? _, Tag tag)
{ {
OpenTags.Pop(); OpenTags.Pop();
if(_CTag_Funcs != null) 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); 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); LastText.Append(ws.Value);
if (_WS_Funcs != null) if (_WS_Funcs != null)
@@ -111,9 +103,9 @@ public class XMLHelper<T> where T : IState
internal void Dispose() internal void Dispose()
{ {
OpenTags = null; OpenTags.Clear();
LastSingleTags = null; LastSingleTags.Clear();
LastText = null; LastText.Clear();
if (_in != null) if (_in != null)
{ {
if (_OTag_Funcs != null) if (_OTag_Funcs != null)

View File

@@ -4,31 +4,17 @@ using HaDocument.Comparers;
using HaDocument.Interfaces; using HaDocument.Interfaces;
using System.Collections.Generic; using System.Collections.Generic;
public class DocumentSearchResult { public class SearchResult {
public Meta MetaData { get; } public string Search { get; private set; }
public List<DocumentResult> Results { get; } 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) { public SearchResult(string search, string index) {
MetaData = meta; Search = search;
Results = new List<DocumentResult>(4); Index = index;
}
}
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);
} }
} }

View File

@@ -1,4 +1,5 @@
namespace HaWeb.Models; namespace HaWeb.Models;
using HaDocument.Models;
public class SucheViewModel { public class SucheViewModel {
public List<(int Year, List<BriefeMetaViewModel> LetterList)>? Letters { get; private set; } 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? ActiveVolume { get; private set; }
public string? ActivePage { get; private set; } public string? ActivePage { get; private set; }
public string? ActiveSearch { get; private set; } public string? ActiveSearch { get; private set; }
public Dictionary<string, List<SearchResult>>? SearchResults { get; private set; }
public SucheViewModel( public SucheViewModel(
List<(int Year, List<BriefeMetaViewModel> LetterList)>? letters, List<(int Year, List<BriefeMetaViewModel> LetterList)>? letters,
@@ -19,7 +21,9 @@ public class SucheViewModel {
List<(string Key, string Name)>? availablePersons, List<(string Key, string Name)>? availablePersons,
List<(string Volume, List<string> Pages)>? availablePages, List<(string Volume, List<string> Pages)>? availablePages,
string? activeVolume, string? activeVolume,
string? activePage string? activePage,
string? activeSearch,
Dictionary<string, List<SearchResult>>? searchResults
) { ) {
Letters = letters; Letters = letters;
if (letters != null) if (letters != null)
@@ -32,5 +36,7 @@ public class SucheViewModel {
AvailablePages = availablePages; AvailablePages = availablePages;
ActiveVolume = activeVolume; ActiveVolume = activeVolume;
ActivePage = activePage; ActivePage = activePage;
ActiveSearch = activeSearch;
SearchResults = searchResults;
} }
} }

View File

@@ -73,4 +73,6 @@ TODO Abhärten des Konstruktors von XMLRootDokument für von außerhalb platzier
TODO XML-Check im Client TODO XML-Check im Client
TODO Lock für die Liste, Bzw ConcurretBag TODO Lock für die Liste, Bzw ConcurretBag
TODO 516A david friedlaender in den traditions TODO 516A david friedlaender in den traditions
TODO 3 Zeilen marginal schließt perfekt an 2 zeilen text an TODO 3 Zeilen marginal schließt perfekt an 2 zeilen text an
TODO Einfügungszeichen zerstört suchergebnisse

View File

@@ -1,76 +1,76 @@
namespace HaWeb; // namespace HaWeb;
using HaDocument.Models; // using HaDocument.Models;
using HaDocument.Interfaces; // using HaDocument.Interfaces;
using HaDocument.Comparers; // using HaDocument.Comparers;
using HaXMLReader.Interfaces; // using HaXMLReader.Interfaces;
using HaWeb.Models; // using HaWeb.Models;
using HaXMLReader.EvArgs; // using HaXMLReader.EvArgs;
using System.Collections.Concurrent; // using System.Collections.Concurrent;
using System.Collections.Generic; // using System.Collections.Generic;
using System.Collections.Immutable; // using System.Collections.Immutable;
using System.Threading.Tasks; // using System.Threading.Tasks;
using System.Linq; // using System.Linq;
using System; // using System;
internal class DocumentSearch { // internal class DocumentSearch {
internal DocumentSearchResult _searchResult { get; } // internal DocumentSearchResult _searchResult { get; }
private IReader _reader { get; } // private IReader _reader { get; }
private string _search { get; } // private string _search { get; }
internal DocumentSearch(DocumentSearchResult res, IReader reader, string search) { // internal DocumentSearch(DocumentSearchResult res, IReader reader, string search) {
_searchResult = res; // _searchResult = res;
_reader = reader; // _reader = reader;
_search = search; // _search = search;
_pg = ""; // _pg = "";
} // }
internal DocumentSearchResult Act() { // internal DocumentSearchResult Act() {
_reader.Text += OnText; // _reader.Text += OnText;
_reader.SingleTag += OnSTag; // _reader.SingleTag += OnSTag;
_reader.Read(); // _reader.Read();
return _searchResult; // return _searchResult;
} // }
internal void OnText(object _, Text text) { // internal void OnText(object _, Text text) {
if (text.Value.ToLower().Contains(_search.ToLower())) { // if (text.Value.ToLower().Contains(_search.ToLower())) {
_searchResult.Results.Add(new DocumentResult(text.Value, _pg, _ln)); // _searchResult.Results.Add(new DocumentResult(text.Value, _pg, _ln));
} // }
} // }
private string _pg; // private string _pg;
private string _ln; // private string _ln;
internal void OnSTag(object _, Tag tag) { // internal void OnSTag(object _, Tag tag) {
if (tag.Name == "page") // if (tag.Name == "page")
_pg = tag["index"]; // _pg = tag["index"];
else if (tag.Name == "line") // else if (tag.Name == "line")
_ln = tag["index"]; // _ln = tag["index"];
} // }
} // }
internal class RegisterSearch { // internal class RegisterSearch {
internal Comment _searchResult { get; } // internal Comment _searchResult { get; }
private IReader _reader { get; } // private IReader _reader { get; }
private string _search { get; } // private string _search { get; }
private bool found; // private bool found;
internal RegisterSearch(Comment res, IReader reader, string search) { // internal RegisterSearch(Comment res, IReader reader, string search) {
_searchResult = res; // _searchResult = res;
_reader = reader; // _reader = reader;
_search = search; // _search = search;
found = false; // found = false;
} // }
internal bool Act() { // internal bool Act() {
_reader.Text += OnText; // _reader.Text += OnText;
_reader.Read(); // _reader.Read();
return found; // return found;
} // }
internal void OnText(object _, Text text) { // internal void OnText(object _, Text text) {
if (text.Value.ToLower().Contains(_search.ToLower())) { // if (text.Value.ToLower().Contains(_search.ToLower())) {
found = true; // found = true;
} // }
} // }
} // }

View File

@@ -0,0 +1,6 @@
namespace HaWeb.SearchHelpers;
public interface ISearchable {
public string Index { get; }
public string SearchText { get; }
}

View 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);
}
})
};
}

View 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() {}
}

View 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;
}
}

View 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();
}
}

View File

@@ -14,7 +14,7 @@
<div class="ha-searchnav"> <div class="ha-searchnav">
@if (Model.AvailableYears != null && Model.AvailableYears.Any()) { @if (Model.AvailableYears != null && Model.AvailableYears.Any()) {
@for(var i = 0; i < Model.AvailableYears.Count; i++) { @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) { @if (Model.AvailableYears[i].StartYear != Model.AvailableYears[i].EndYear) {
<span> <span>
@Model.AvailableYears[i].StartYear-@Model.AvailableYears[i].EndYear @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"> <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)) @await Html.PartialAsync("/Views/Shared/_LetterHead.cshtml", (letter, true))
</a> </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> </div>
@@ -148,7 +154,7 @@
} }
</div> </div>
<form class="ha-searchform" id="ha-searchform"> <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> <button id="ha-searchformsubmit" type="submit">Suchen</button>
</form> </form>
</div> </div>
@@ -164,7 +170,7 @@
const SUBMITSEARCH = function(filter) { const SUBMITSEARCH = function(filter) {
let f = filter.value; let f = filter.value;
window.location.href = "/Suche/" + f; window.location.href = "/Suche?search=" + f;
} }
window.addEventListener("load", () => { window.addEventListener("load", () => {
@@ -192,7 +198,7 @@
<div class="ha-personlist"> <div class="ha-personlist">
<a class="ha-personlistperson @(Model.ActivePerson == null ? "active" : "")" asp-controller="Suche" asp-action="Index">Alle</a> <a class="ha-personlistperson @(Model.ActivePerson == null ? "active" : "")" asp-controller="Suche" asp-action="Index">Alle</a>
@foreach (var person in Model.AvailablePersons) { @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 @person.Name
</a> </a>
} }