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