diff --git a/controllers/index.go b/controllers/index.go index 02e50de..3c04893 100644 --- a/controllers/index.go +++ b/controllers/index.go @@ -8,6 +8,9 @@ import ( const DEFAULT_YEAR = 1765 func GetIndex(fiber *fiber.Ctx) error { - _ = xmlmodels.Get() - return fiber.Render("/", nil) + lib := xmlmodels.Get() + // Years + years, yearmap := lib.Years() + + return fiber.Render("/", map[string]any{"years": years, "yearmap": yearmap}) } diff --git a/lenz.go b/lenz.go index 0b26ba0..ef2a694 100644 --- a/lenz.go +++ b/lenz.go @@ -43,12 +43,14 @@ func main() { } // INFO: the lib, engine and storage objects passed to the server should never be recreated. - err = xmlmodels.New(dir, commit.Hash) + lib, err := xmlmodels.Parse(dir, commit.Hash) if err != nil { panic(err) } engine := templating.New(&views.LayoutFS, &views.RoutesFS) + engine.AddFuncs(lib.FuncMap()) + storage := memory.New(memory.Config{ GCInterval: 24 * time.Hour, }) diff --git a/templating/engine.go b/templating/engine.go index bb99d0a..599b88f 100644 --- a/templating/engine.go +++ b/templating/engine.go @@ -11,7 +11,6 @@ import ( "sync" "github.com/Theodor-Springmann-Stiftung/lenz-web/helpers/functions" - "github.com/gofiber/fiber/v2" "golang.org/x/net/websocket" ) @@ -180,9 +179,17 @@ func (e *Engine) AddFunc(name string, fn any) { e.FuncMap[name] = fn } +func (e *Engine) AddFuncs(funcs template.FuncMap) { + e.mu.Lock() + defer e.mu.Unlock() + for k, v := range funcs { + e.FuncMap[k] = v + } +} + func (e *Engine) Render(out io.Writer, path string, data any, layout ...string) error { e.mu.RLock() - ld := data.(fiber.Map) + ld := data.(map[string]any) if e.GlobalData != nil { maps.Copy(ld, e.GlobalData) } diff --git a/views/routes/body.gohtml b/views/routes/body.gohtml index 9b12097..9f110b2 100644 --- a/views/routes/body.gohtml +++ b/views/routes/body.gohtml @@ -1 +1,15 @@ -Hello from body! What! +{{ $model := . }} + +{{- if .years -}} + {{- range $y := .years -}} +
+ {{- printf "%d " $y -}} + {{- $letters := index $model.yearmap $y -}} + {{- len $letters }} + {{ range $l := $letters -}} +
{{ $l.Letter }}
+
{{ $l.Earliest.Text -}}
+ {{- end -}} +
+ {{- end -}} +{{- end -}} diff --git a/xml/xmlprovider.go b/xml/xmlprovider.go index b968597..d6ad97b 100644 --- a/xml/xmlprovider.go +++ b/xml/xmlprovider.go @@ -89,13 +89,13 @@ func (p *XMLParser[T]) Serialize(dataholder XMLRootElement[T], path string, late // It deletes all items that have not been parsed in the last commit, // and whose filepath has not been marked as failed. func (p *XMLParser[T]) Cleanup(latest ParseMeta) { - todelete := make([]string, 0) + todelete := make([]any, 0) toappend := make([]*T, 0) p.Infos.Range(func(key, value interface{}) bool { info := value.(ItemInfo) if !info.Parse.Equals(latest) { if !latest.Failed(info.Source) { - todelete = append(todelete, key.(string)) + todelete = append(todelete, key) } else { item, ok := p.Items.Load(key) if ok { @@ -165,7 +165,7 @@ func (p *XMLParser[T]) Info(id string) ItemInfo { return info.(ItemInfo) } -func (p *XMLParser[T]) Item(id string) *T { +func (p *XMLParser[T]) Item(id any) *T { item, ok := p.Items.Load(id) if !ok { return nil diff --git a/xml/xsdtime.go b/xml/xsdtime.go index 6d498f2..bd4e4e2 100644 --- a/xml/xsdtime.go +++ b/xml/xsdtime.go @@ -231,17 +231,17 @@ func (xsdd XSDDate) Type() XSDDatetype { } func (xsdd *XSDDate) Validate() bool { - if xsdd.error { + if xsdd.error || len(xsdd.base) == 0 { xsdd.state = Invalid return false } xsdd.state = xsdd.inferState() - if xsdd.state != Invalid { - return true + if xsdd.state == Invalid { + return false } - return false + return true } func (xsdd *XSDDate) parseError(s string) error { @@ -309,6 +309,56 @@ func (xsdd XSDDate) inferState() XSDDatetype { return Invalid } +func (xsdd XSDDate) Before(other XSDDate) bool { + if xsdd.Year < other.Year { + return true + } else if xsdd.Year > other.Year { + return false + } + + if xsdd.Month < other.Month { + return true + } else if xsdd.Month > other.Month { + return false + } + + if xsdd.Day < other.Day { + return true + } + + return false +} + +func (xsddate *XSDDate) Compare(other *XSDDate) int { + if !xsddate.Validate() { + return -1 + } + + if !other.Validate() { + return 1 + } + + if xsddate.Year < other.Year { + return -1 + } else if xsddate.Year > other.Year { + return 1 + } + + if xsddate.Month < other.Month { + return -1 + } else if xsddate.Month > other.Month { + return 1 + } + + if xsddate.Day < other.Day { + return -1 + } else if xsddate.Day > other.Day { + return 1 + } + + return 0 +} + func validDay(i int) bool { if i < 1 || i > 31 { return false diff --git a/xmlmodels/common.go b/xmlmodels/common.go index 2a47b3d..bfb2832 100644 --- a/xmlmodels/common.go +++ b/xmlmodels/common.go @@ -17,3 +17,23 @@ type Date struct { Cert string `xml:"cert,attr"` Text string `xml:",chardata"` } + +func (d *Date) Sort() *xmlparsing.XSDDate { + if d.NotBefore.Validate() { + return &d.NotBefore + } + if d.From.Validate() { + return &d.From + } + if d.When.Validate() { + return &d.When + } + if d.To.Validate() { + return &d.To + } + if d.NotAfter.Validate() { + return &d.NotAfter + } + + return nil +} diff --git a/xmlmodels/library.go b/xmlmodels/library.go index 8269a85..93bc1a4 100644 --- a/xmlmodels/library.go +++ b/xmlmodels/library.go @@ -2,8 +2,11 @@ package xmlmodels import ( "fmt" + "html/template" "log/slog" + "maps" "path/filepath" + "slices" "strconv" "strings" "sync" @@ -30,6 +33,8 @@ type Library struct { Letters *xmlparsing.XMLParser[Letter] Traditions *xmlparsing.XMLParser[Tradition] Metas *xmlparsing.XMLParser[Meta] + + cache sync.Map } func (l *Library) String() string { @@ -89,6 +94,7 @@ func (l *Library) Parse(source xmlparsing.ParseSource, baseDir, commit string) e // INFO: this lock prevents multiple parses from happening at the same time. l.mu.Lock() defer l.mu.Unlock() + l.cache.Clear() wg := sync.WaitGroup{} meta := xmlparsing.ParseMeta{ @@ -233,3 +239,73 @@ func (l *Library) cleanup(meta xmlparsing.ParseMeta) { wg.Wait() } + +func (l *Library) Years() ([]int, map[int][]Meta) { + if years, ok := l.cache.Load("years"); ok { + if yearmap, ok := l.cache.Load("yearmap"); ok { + return years.([]int), yearmap.(map[int][]Meta) + } + } + + mapYears := make(map[int][]Meta) + for item := range l.Metas.Iterate() { + earliest := item.Earliest() + if earliest != nil { + mapYears[earliest.Sort().Year] = append(mapYears[earliest.Sort().Year], item) + } + } + + ret := slices.Collect(maps.Keys(mapYears)) + slices.Sort(ret) + + for _, items := range mapYears { + slices.SortFunc(items, func(a, b Meta) int { + return a.Earliest().Sort().Compare(b.Earliest().Sort()) + }) + } + l.cache.Store("years", ret) + l.cache.Store("yearmap", mapYears) + return ret, mapYears +} + +func (l *Library) LettersForYear(year int) (ret []Meta) { + for l := range l.Metas.Filter(func(item Meta) bool { + return item.Earliest().Sort().Year == year + }) { + ret = append(ret, l) + } + return +} + +func (l *Library) Person(id int) (ret *PersonDef) { + ret = l.Persons.Item(id) + return +} + +func (l *Library) Place(id int) (ret *LocationDef) { + ret = l.Places.Item(id) + return +} + +func (l *Library) GetPersons(id []int) (ret []*PersonDef) { + for _, i := range id { + ret = append(ret, l.Person(i)) + } + return +} + +func (l *Library) GetPlaces(id []int) (ret []*LocationDef) { + for _, i := range id { + ret = append(ret, l.Place(i)) + } + return +} + +func (l *Library) FuncMap() template.FuncMap { + return template.FuncMap{ + "Person": l.Person, + "Place": l.Place, + "Persons": l.GetPersons, + "Places": l.GetPlaces, + } +} diff --git a/xmlmodels/meta.go b/xmlmodels/meta.go index c13f66c..b658fa8 100644 --- a/xmlmodels/meta.go +++ b/xmlmodels/meta.go @@ -3,6 +3,7 @@ package xmlmodels import ( "encoding/json" "encoding/xml" + "iter" xmlparsing "github.com/Theodor-Springmann-Stiftung/lenz-web/xml" ) @@ -17,6 +18,28 @@ type Meta struct { Recieved []Action `xml:"recieved"` } +func (m *Meta) Earliest() *Date { + var earliest *Date + + for _, action := range m.Sent { + if earliest == nil || action.Earliest().Sort().Before(*earliest.Sort()) { + earliest = action.Earliest() + } + } + + if earliest != nil { + return earliest + } + + for _, action := range m.Recieved { + if earliest == nil || action.Earliest().Sort().Before(*earliest.Sort()) { + earliest = action.Earliest() + } + } + + return earliest +} + func (m Meta) Keys() []any { return []any{m.Letter} } @@ -33,8 +56,30 @@ func (m Meta) String() string { return string(json) } +func (m Meta) SendRecieved() iter.Seq2[*Action, *Action] { + return func(yield func(*Action, *Action) bool) { + for i, sent := range m.Sent { + var rec *Action + if i < len(m.Recieved) { + rec = &m.Recieved[i] + } + if !yield(&sent, rec) { + return + } + } + } +} + type Action struct { - Dates []Date `xml:"date,attr"` + Dates []Date `xml:"date"` Places []RefElement `xml:"place"` Persons []RefElement `xml:"person"` } + +func (a *Action) Earliest() *Date { + if len(a.Dates) == 0 { + return nil + } + + return &a.Dates[0] +} diff --git a/xmlmodels/public.go b/xmlmodels/public.go index f6189b3..a280663 100644 --- a/xmlmodels/public.go +++ b/xmlmodels/public.go @@ -27,14 +27,14 @@ func Get() *Library { return lib } -func New(dir, hash string) error { - Set(NewLibrary()) - return Parse(dir, hash) -} - -func Parse(dir, hash string) error { - if hash == "" { - return lib.Parse(xmlparsing.Path, dir, hash) +func Parse(dir, hash string) (*Library, error) { + if lib == nil { + Set(NewLibrary()) } - return lib.Parse(xmlparsing.Commit, dir, hash) + + if hash == "" { + return Get(), lib.Parse(xmlparsing.Path, dir, hash) + } + + return Get(), lib.Parse(xmlparsing.Commit, dir, hash) }