XML parsing overhaul

This commit is contained in:
Simon Martens
2024-11-22 00:35:27 +01:00
parent b93256c522
commit bc244fbad4
26 changed files with 507 additions and 352 deletions

View File

@@ -85,8 +85,7 @@ func (k *KGPZ) Enrich() error {
} }
// INFO: We pass agents by value since we don't want to block the library // INFO: We pass agents by value since we don't want to block the library
agents := make([]xmlprovider.Agent, len(k.Library.Agents.Items.Agents)) agents := k.Library.Agents.All()
_ = copy(agents, k.Library.Agents.Items.Agents)
go func(agents []xmlprovider.Agent) { go func(agents []xmlprovider.Agent) {
k.GND.FetchPersons(agents) k.GND.FetchPersons(agents)
k.GND.WriteCache(k.Config.GNDPath) k.GND.WriteCache(k.Config.GNDPath)

View File

@@ -21,7 +21,7 @@ func GetIssue(kgpz *app.KGPZ) fiber.Handler {
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)
} }
issue, err := viewmodels.IssueView(y, d, kgpz.Library) issue, err := viewmodels.NewSingleIssueView(y, d, kgpz.Library)
if err != nil { if err != nil {
logging.Error(err, "Issue could not be found") logging.Error(err, "Issue could not be found")

66
functions/date.go Normal file
View File

@@ -0,0 +1,66 @@
package functions
import (
"time"
)
const TLAYOUT = "2006-01-02"
var TRANSLM = [][]string{
{"Januar", "Jan", "1"},
{"Februar", "Feb", "2"},
{"März", "Mär", "3"},
{"April", "Apr", "4"},
{"Mai", "Mai", "5"},
{"Juni", "Jun", "6"},
{"Juli", "Jul", "7"},
{"August", "Aug", "8"},
{"September", "Sep", "9"},
{"Oktober", "Okt", "10"},
{"November", "Nov", "11"},
{"Dezember", "Dez", "12"},
}
var TRANSLD = [][]string{
{"Montag", "Mo"},
{"Dienstag", "Di"},
{"Mittwoch", "Mi"},
{"Donnerstag", "Do"},
{"Freitag", "Fr"},
{"Samstag", "Sa"},
{"Sonntag", "So"},
}
type Date struct {
Month string
Mon string
MonthNo string
DayNo int
Weekday string
Wd string
}
func GetDate(d string) Date {
t, err := time.Parse(TLAYOUT, d)
if err != nil {
return Date{}
}
m := int(t.Month()) - 1
wd := int(t.Weekday()) - 1
return Date{
Month: TRANSLM[m][0],
Mon: TRANSLM[m][1],
MonthNo: TRANSLM[m][2],
DayNo: t.Day(),
Weekday: TRANSLD[wd][0],
Wd: TRANSLD[wd][1],
}
}
func MonthName(m int) string {
return TRANSLM[m-1][0]
}
func MonthNameShort(m int) string {
return TRANSLM[m-1][1]
}

2
go.mod
View File

@@ -22,7 +22,7 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.5.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect github.com/klauspost/compress v1.17.0 // indirect

2
go.sum
View File

@@ -46,6 +46,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=

View File

@@ -11,6 +11,8 @@ import (
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/server" "github.com/Theodor-Springmann-Stiftung/kgpz_web/server"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/templating"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/views"
) )
const ( const (
@@ -44,7 +46,9 @@ func main() {
kgpz := app.NewKGPZ(cfg) kgpz := app.NewKGPZ(cfg)
kgpz.Init() kgpz.Init()
server := server.Create(kgpz, cfg) engine := templating.NewEngine(&views.LayoutFS, &views.RoutesFS, kgpz)
server := server.Create(kgpz, cfg, engine)
Start(kgpz, server) Start(kgpz, server)
} }

View File

@@ -25,6 +25,8 @@ type GNDProvider struct {
mu sync.Mutex mu sync.Mutex
Persons sync.Map Persons sync.Map
// INFO: this holds all errors that occured during fetching
// and is used to prevent further fetches of the same person.
errmu sync.Mutex errmu sync.Mutex
errs map[string]int errs map[string]int
} }
@@ -98,7 +100,6 @@ func (p *GNDProvider) readPerson(file string) {
p.Persons.Store(person.Agent.GND, person) p.Persons.Store(person.Agent.GND, person)
return return
} }
} }
func (p *GNDProvider) WriteCache(folder string) error { func (p *GNDProvider) WriteCache(folder string) error {
@@ -134,6 +135,7 @@ func (p *GNDProvider) writePersons(folder string) error {
return nil return nil
} }
// INFO: this overwrites any existing files
func (p *GNDProvider) writePerson(folder, id string, person Person) { func (p *GNDProvider) writePerson(folder, id string, person Person) {
// JSON marshalling of the person and sanity check: // JSON marshalling of the person and sanity check:
filepath := filepath.Join(folder, person.KGPZID+".json") filepath := filepath.Join(folder, person.KGPZID+".json")
@@ -209,9 +211,10 @@ func (p *GNDProvider) fetchPerson(person xmlprovider.Agent) {
var response *http.Response var response *http.Response
// INFO: we do 3 retries with increasing time between them
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
response, err = http.DefaultClient.Do(request) response, err = http.DefaultClient.Do(request)
if err == nil && 400 > response.StatusCode { if err == nil && response.StatusCode < 400 {
if i > 0 { if i > 0 {
logging.Info("Successfully fetched person: " + person.ID + " after " + strconv.Itoa(i) + " retries") logging.Info("Successfully fetched person: " + person.ID + " after " + strconv.Itoa(i) + " retries")
} }
@@ -245,7 +248,7 @@ func (p *GNDProvider) fetchPerson(person xmlprovider.Agent) {
return return
} }
// Wirte response body to file: // For debug purposes: Write response body to file:
// os.WriteFile("gnd_responses/"+person.ID+".json", body, 0644) // os.WriteFile("gnd_responses/"+person.ID+".json", body, 0644)
gndPerson := Person{} gndPerson := Person{}

View File

@@ -9,65 +9,65 @@ import (
type Person struct { type Person struct {
KGPZID string `json:"kgpzid"` KGPZID string `json:"kgpzid"`
Agent xmlprovider.Agent `json:"agent"` Agent xmlprovider.Agent `json:"agent"`
URL string `json:"id"` URL string `json:"id,omitempty"`
DateOfBirth []string `json:"dateOfBirth"` DateOfBirth []string `json:"dateOfBirth,omitempty"`
PlaceOfBirth []Entity `json:"placeOfBirth"` PlaceOfBirth []Entity `json:"placeOfBirth,omitempty"`
DateOfDeath []string `json:"dateOfDeath"` DateOfDeath []string `json:"dateOfDeath,omitempty"`
PlaceOfDeath []Entity `json:"placeOfDeath"` PlaceOfDeath []Entity `json:"placeOfDeath,omitempty"`
PlaceOfBirthAsLiteral []string `json:"placeOfBirthAsLiteral"` PlaceOfBirthAsLiteral []string `json:"placeOfBirthAsLiteral,omitempty"`
PlaceOfDeathAsLiteral []string `json:"placeOfDeathAsLiteral"` PlaceOfDeathAsLiteral []string `json:"placeOfDeathAsLiteral,omitempty"`
BiographicalOrHistoricalInformation []string `json:"biographicalOrHistoricalInformation"` BiographicalOrHistoricalInformation []string `json:"biographicalOrHistoricalInformation,omitempty"`
PreferredName string `json:"preferredName"` PreferredName string `json:"preferredName,omitempty"`
GndIdentifier string `json:"gndIdentifier"` GndIdentifier string `json:"gndIdentifier,omitempty"`
Wikipedia []Entity `json:"wikipedia"` Wikipedia []Entity `json:"wikipedia,omitempty"`
Depiction []Picture `json:"depiction"` Depiction []Picture `json:"depiction,omitempty"`
ProfessionOrOccupation []Entity `json:"professionOrOccupation"` ProfessionOrOccupation []Entity `json:"professionOrOccupation,omitempty"`
PreferredNameEntityForThePerson PersonNameEntity `json:"preferredNameEntityForThePerson"` PreferredNameEntityForThePerson PersonNameEntity `json:"preferredNameEntityForThePerson,omitempty"`
VariantNameEntityForThePerson []PersonNameEntity `json:"variantNameEntityForThePerson"` VariantNameEntityForThePerson []PersonNameEntity `json:"variantNameEntityForThePerson,omitempty"`
VariantName []string `json:"variantName"` VariantName []string `json:"variantName,omitempty"`
SameAs []CrossReferences `json:"sameAs"` SameAs []CrossReferences `json:"sameAs,omitempty"`
Pseudonym []Entity `json:"pseudonym"` Pseudonym []Entity `json:"pseudonym,omitempty"`
GNDSubjectCategory []Entity `json:"gndSubjectCategory"` GNDSubjectCategory []Entity `json:"gndSubjectCategory,omitempty"`
Type []string `json:"type"` Type []string `json:"type,omitempty"`
PlaceOfActivity []Entity `json:"placeOfActivity"` PlaceOfActivity []Entity `json:"placeOfActivity,omitempty"`
} }
type CrossReferences struct { type CrossReferences struct {
Items Collection `json:"collection"` Items Collection `json:"collection,omitempty"`
ID string `json:"id"` ID string `json:"id,omitempty"`
} }
type Collection struct { type Collection struct {
Abbr string `json:"abbr"` Abbr string `json:"abbr,omitempty"`
Name string `json:"name"` Name string `json:"name,omitempty"`
Publisher string `json:"publisher"` Publisher string `json:"publisher,omitempty"`
Icon string `json:"icon"` Icon string `json:"icon,omitempty"`
ID string `json:"id"` ID string `json:"id,omitempty"`
} }
type Link struct { type Link struct {
ID string `json:"id"` ID string `json:"id,omitempty"`
Label string `json:"label"` Label string `json:"label,omitempty"`
} }
type Picture struct { type Picture struct {
ID string `json:"id"` ID string `json:"id,omitempty"`
URL string `json:"url"` URL string `json:"url,omitempty"`
Thumbnail string `json:"thumbnail"` Thumbnail string `json:"thumbnail,omitempty"`
} }
type Entity struct { type Entity struct {
ID string `json:"id"` ID string `json:"id,omitempty"`
Label string `json:"label"` Label string `json:"label,omitempty"`
} }
type PersonNameEntity struct { type PersonNameEntity struct {
Prefix []string `json:"prefix"` Prefix []string `json:"prefix,omitempty"`
Counting []string `json:"counting"` Counting []string `json:"counting,omitempty"`
Forename []string `json:"forename"` Forename []string `json:"forename,omitempty"`
Surname []string `json:"surname"` Surname []string `json:"surname,omitempty"`
PersonalName []string `json:"personalName"` PersonalName []string `json:"personalName,omitempty"`
NameAddition []string `json:"nameAddition"` NameAddition []string `json:"nameAddition,omitempty"`
} }
func (p Person) String() string { func (p Person) String() string {

View File

@@ -5,10 +5,6 @@ import (
"fmt" "fmt"
) )
type AgentProvider struct {
XMLProvider[Agents]
}
type Agent struct { type Agent struct {
XMLName xml.Name `xml:"akteur"` XMLName xml.Name `xml:"akteur"`
Names []string `xml:"name"` Names []string `xml:"name"`
@@ -20,29 +16,6 @@ type Agent struct {
AnnotationNote AnnotationNote
} }
type Agents struct { func (a Agent) String() string {
XMLName xml.Name `xml:"akteure"`
Agents []Agent `xml:"akteur"`
}
func (a Agents) String() string {
var res []string
for _, agent := range a.Agents {
res = append(res, agent.String())
}
return fmt.Sprintf("Agents: %v", res)
}
func (a Agents) Append(data Agents) Agents {
a.Agents = append(a.Agents, data.Agents...)
return a
}
func (a *Agent) String() string {
return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nLife: %s\nGND: %s\nAnnotations: %v\nNotes: %v\n", a.ID, a.Names, a.SortName, a.Life, a.GND, a.Annotations, a.Notes) return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nLife: %s\nGND: %s\nAnnotations: %v\nNotes: %v\n", a.ID, a.Names, a.SortName, a.Life, a.GND, a.Annotations, a.Notes)
} }
func NewAgentProvider(paths []string) *AgentProvider {
return &AgentProvider{XMLProvider: XMLProvider[Agents]{paths: paths}}
}

View File

@@ -5,15 +5,6 @@ import (
"fmt" "fmt"
) )
type CategoryProvider struct {
XMLProvider[Categories]
}
type Categories struct {
XMLName xml.Name `xml:"kategorien"`
Category []Category `xml:"kategorie"`
}
type Category struct { type Category struct {
XMLName xml.Name `xml:"kategorie"` XMLName xml.Name `xml:"kategorie"`
Names []string `xml:"name"` Names []string `xml:"name"`
@@ -22,24 +13,6 @@ type Category struct {
AnnotationNote AnnotationNote
} }
func (c Categories) Append(data Categories) Categories { func (c Category) String() string {
c.Category = append(c.Category, data.Category...)
return c
}
func (c Categories) String() string {
var res []string
for _, category := range c.Category {
res = append(res, category.String())
}
return fmt.Sprintf("Categories: %v", res)
}
func (c *Category) String() string {
return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nAnnotations: %v\nNotes: %v\n", c.ID, c.Names, c.SortName, c.Annotations, c.Notes) return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nAnnotations: %v\nNotes: %v\n", c.ID, c.Names, c.SortName, c.Annotations, c.Notes)
} }
func NewCategoryProvider(paths []string) *CategoryProvider {
return &CategoryProvider{XMLProvider: XMLProvider[Categories]{paths: paths}}
}

View File

@@ -3,17 +3,9 @@ package xmlprovider
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"strconv"
) )
type IssueProvider struct {
XMLProvider[Issues]
}
type Issues struct {
XMLName xml.Name `xml:"stuecke"`
Issues []Issue `xml:"stueck"`
}
type Issue struct { type Issue struct {
XMLName xml.Name `xml:"stueck"` XMLName xml.Name `xml:"stueck"`
Number Nummer `xml:"nummer"` Number Nummer `xml:"nummer"`
@@ -37,24 +29,20 @@ type Additional struct {
Bis string `xml:"bis"` Bis string `xml:"bis"`
} }
func (i Issues) Append(data Issues) Issues { func (i Issue) GetIDs() []string {
i.Issues = append(i.Issues, data.Issues...) res := make([]string, 2)
return i date := i.Datum.When
} if date != "" {
res = append(res, date)
func (i Issues) String() string {
var res []string
for _, issue := range i.Issues {
res = append(res, issue.String())
} }
return fmt.Sprintf("Issues: %v", res) if len(date) > 4 {
res = append(res, i.Datum.When[0:4]+"-"+strconv.Itoa(i.Number.No))
}
return res
} }
func (i Issue) String() string { func (i Issue) String() string {
return fmt.Sprintf("Number: %v, Datum: %v, Von: %d, Bis: %d, Additionals: %v, Identifier: %v, AnnotationNote: %v\n", i.Number, i.Datum, i.Von, i.Bis, i.Additionals, i.Identifier, i.AnnotationNote) return fmt.Sprintf("Number: %v, Datum: %v, Von: %d, Bis: %d, Additionals: %v, Identifier: %v, AnnotationNote: %v\n", i.Number, i.Datum, i.Von, i.Bis, i.Additionals, i.Identifier, i.AnnotationNote)
} }
func NewIssueProvider(paths []string) *IssueProvider {
return &IssueProvider{XMLProvider: XMLProvider[Issues]{paths: paths}}
}

View File

@@ -3,17 +3,10 @@ package xmlprovider
import ( import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"github.com/google/uuid"
) )
type PieceProvider struct {
XMLProvider[Pieces]
}
type Pieces struct {
XMLName xml.Name `xml:"beitraege"`
Piece []Piece `xml:"beitrag"`
}
type Piece struct { type Piece struct {
XMLName xml.Name `xml:"beitrag"` XMLName xml.Name `xml:"beitrag"`
IssueRefs []IssueRef `xml:"stueck"` IssueRefs []IssueRef `xml:"stueck"`
@@ -30,24 +23,18 @@ type Piece struct {
AnnotationNote AnnotationNote
} }
func (p Pieces) Append(data Pieces) Pieces {
p.Piece = append(p.Piece, data.Piece...)
return p
}
func (p Pieces) String() string {
var res []string
for _, piece := range p.Piece {
res = append(res, piece.String())
}
return fmt.Sprintf("Pieces: %v", res)
}
func (p Piece) String() string { func (p Piece) String() string {
return fmt.Sprintf("ID: %s\nIssueRefs: %v\nPlaceRefs: %v\nCategoryRefs: %v\nAgentRefs: %v\nWorkRefs: %v\nPieceRefs: %v\nAdditionalRef: %v\nIncipit: %v\nTitle: %v\nAnnotations: %v\nNotes: %v\n", p.ID, p.IssueRefs, p.PlaceRefs, p.CategoryRefs, p.AgentRefs, p.WorkRefs, p.PieceRefs, p.AdditionalRef, p.Incipit, p.Title, p.Annotations, p.Notes) return fmt.Sprintf("ID: %s\nIssueRefs: %v\nPlaceRefs: %v\nCategoryRefs: %v\nAgentRefs: %v\nWorkRefs: %v\nPieceRefs: %v\nAdditionalRef: %v\nIncipit: %v\nTitle: %v\nAnnotations: %v\nNotes: %v\n", p.ID, p.IssueRefs, p.PlaceRefs, p.CategoryRefs, p.AgentRefs, p.WorkRefs, p.PieceRefs, p.AdditionalRef, p.Incipit, p.Title, p.Annotations, p.Notes)
} }
func NewPieceProvider(paths []string) *PieceProvider { func (p Piece) GetIDs() []string {
return &PieceProvider{XMLProvider: XMLProvider[Pieces]{paths: paths}} ret := make([]string, 2)
if p.ID != "" {
ret = append(ret, p.ID)
}
// TODO: sensible IDs
uid := uuid.New()
ret = append(ret, uid.String())
return ret
} }

View File

@@ -5,15 +5,6 @@ import (
"fmt" "fmt"
) )
type PlaceProvider struct {
XMLProvider[Places]
}
type Places struct {
XMLName xml.Name `xml:"orte"`
Place []Place `xml:"ort"`
}
type Place struct { type Place struct {
XMLName xml.Name `xml:"ort"` XMLName xml.Name `xml:"ort"`
Names []string `xml:"name"` Names []string `xml:"name"`
@@ -23,24 +14,6 @@ type Place struct {
AnnotationNote AnnotationNote
} }
func (p Places) Append(data Places) Places { func (p Place) String() string {
p.Place = append(p.Place, data.Place...)
return p
}
func (p Places) String() string {
var res []string
for _, place := range p.Place {
res = append(res, place.String())
}
return fmt.Sprintf("Places: %v", res)
}
func (p *Place) String() string {
return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nGeo: %s\nAnnotations: %v\nNotes: %v\n", p.ID, p.Names, p.SortName, p.Geo, p.Annotations, p.Notes) return fmt.Sprintf("ID: %s\nNames: %v\nSortName: %s\nGeo: %s\nAnnotations: %v\nNotes: %v\n", p.ID, p.Names, p.SortName, p.Geo, p.Annotations, p.Notes)
} }
func NewPlaceProvider(paths []string) *PlaceProvider {
return &PlaceProvider{XMLProvider: XMLProvider[Places]{paths: paths}}
}

View File

@@ -0,0 +1,111 @@
package xmlprovider
import "encoding/xml"
// INFO: These are just root elements that hold the data of the XML files.
// They get discarded after a parse.
type XMLRootElement[T any] interface {
Children() []T
}
type AgentRoot struct {
XMLName xml.Name `xml:"akteure"`
Agents []Agent `xml:"akteur"`
}
func NewAgentRoot() *AgentRoot {
return &AgentRoot{}
}
func (a AgentRoot) New() *AgentRoot {
return NewAgentRoot()
}
func (a AgentRoot) Children() []Agent {
return a.Agents
}
type PlaceRoot struct {
XMLName xml.Name `xml:"orte"`
Place []Place `xml:"ort"`
}
func NewPlaceRoot() *PlaceRoot {
return &PlaceRoot{}
}
func (p PlaceRoot) New() *PlaceRoot {
return NewPlaceRoot()
}
func (p PlaceRoot) Children() []Place {
return p.Place
}
type CategoryRoot struct {
XMLName xml.Name `xml:"kategorien"`
Category []Category `xml:"kategorie"`
}
func NewCategoryRoot() *CategoryRoot {
return &CategoryRoot{}
}
func (c CategoryRoot) New() XMLRootElement[Category] {
return NewCategoryRoot()
}
func (c CategoryRoot) Children() []Category {
return c.Category
}
type PieceRoot struct {
XMLName xml.Name `xml:"beitraege"`
Piece []Piece `xml:"beitrag"`
}
func NewPieceRoot() *PieceRoot {
return &PieceRoot{}
}
func (p PieceRoot) New() XMLRootElement[Piece] {
return NewPieceRoot()
}
func (p PieceRoot) Children() []Piece {
return p.Piece
}
type IssueRoot struct {
XMLName xml.Name `xml:"stuecke"`
Issues []Issue `xml:"stueck"`
}
func NewIssueRoot() *IssueRoot {
return &IssueRoot{}
}
func (i IssueRoot) New() XMLRootElement[Issue] {
return NewIssueRoot()
}
func (i IssueRoot) Children() []Issue {
return i.Issues
}
type WorkRoot struct {
XMLName xml.Name `xml:"werke"`
Work []Work `xml:"werk"`
}
func NewWorkRoot() *WorkRoot {
return &WorkRoot{}
}
func (w WorkRoot) New() XMLRootElement[Work] {
return NewWorkRoot()
}
func (w WorkRoot) Children() []Work {
return w.Work
}

View File

@@ -5,15 +5,6 @@ import (
"fmt" "fmt"
) )
type WorkProvider struct {
XMLProvider[Works]
}
type Works struct {
XMLName xml.Name `xml:"werke"`
Work []Work `xml:"werk"`
}
type Work struct { type Work struct {
XMLName xml.Name `xml:"werk"` XMLName xml.Name `xml:"werk"`
URLs []URL `xml:"url"` URLs []URL `xml:"url"`
@@ -32,24 +23,6 @@ type Citation struct {
Inner Inner
} }
func (w Works) Append(data Works) Works { func (w Work) String() string {
w.Work = append(w.Work, data.Work...)
return w
}
func (w Works) String() string {
var res []string
for _, work := range w.Work {
res = append(res, work.String())
}
return fmt.Sprintf("Works: %v", res)
}
func (w *Work) String() string {
return fmt.Sprintf("URLs: %v, Citation: %v, PreferredTitle: %s, Akteur: %v, Identifier: %v, AnnotationNote: %v\n", w.URLs, w.Citation, w.PreferredTitle, w.Akteur, w.Identifier, w.AnnotationNote) return fmt.Sprintf("URLs: %v, Citation: %v, PreferredTitle: %s, Akteur: %v, Identifier: %v, AnnotationNote: %v\n", w.URLs, w.Citation, w.PreferredTitle, w.Akteur, w.Identifier, w.AnnotationNote)
} }
func NewWorkProvider(paths []string) *WorkProvider {
return &WorkProvider{XMLProvider: XMLProvider[Works]{paths: paths}}
}

View File

@@ -39,6 +39,10 @@ type Identifier struct {
ID string `xml:"id,attr"` ID string `xml:"id,attr"`
} }
func (i Identifier) GetIDs() []string {
return []string{i.ID}
}
type Reference struct { type Reference struct {
Ref string `xml:"ref,attr"` Ref string `xml:"ref,attr"`
Category string `xml:"kat,attr"` Category string `xml:"kat,attr"`

View File

@@ -10,34 +10,40 @@ import (
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
) )
type KGPZXML[T any] interface { type XMLItem interface {
Append(data T) T
fmt.Stringer fmt.Stringer
GetIDs() []string
} }
type XMLProvider[T KGPZXML[T]] struct { type XMLProvider[T XMLItem] struct {
Paths []string
Items sync.Map
mu sync.Mutex mu sync.Mutex
paths []string
Items T
} }
type Library struct { type Library struct {
Agents *AgentProvider Agents *XMLProvider[Agent]
Places *PlaceProvider Places *XMLProvider[Place]
Works *WorkProvider Works *XMLProvider[Work]
Categories *CategoryProvider Categories *XMLProvider[Category]
Issues *IssueProvider Issues *XMLProvider[Issue]
Pieces *PieceProvider Pieces *XMLProvider[Piece]
}
func (l *Library) String() string {
return fmt.Sprintf("Agents: %s\nPlaces: %s\nWorks: %s\nCategories: %s\nIssues: %s\nPieces: %s\n",
l.Agents.String(), l.Places.String(), l.Works.String(), l.Categories.String(), l.Issues.String(), l.Pieces.String())
} }
func NewLibrary(agentpaths, placepaths, workpaths, categorypaths, issuepaths, piecepaths []string) *Library { func NewLibrary(agentpaths, placepaths, workpaths, categorypaths, issuepaths, piecepaths []string) *Library {
return &Library{ return &Library{
Agents: NewAgentProvider(agentpaths), Agents: &XMLProvider[Agent]{Paths: agentpaths},
Places: NewPlaceProvider(placepaths), Places: &XMLProvider[Place]{Paths: placepaths},
Works: NewWorkProvider(workpaths), Works: &XMLProvider[Work]{Paths: workpaths},
Categories: NewCategoryProvider(categorypaths), Categories: &XMLProvider[Category]{Paths: categorypaths},
Issues: NewIssueProvider(issuepaths), Issues: &XMLProvider[Issue]{Paths: issuepaths},
Pieces: NewPieceProvider(piecepaths), Pieces: &XMLProvider[Piece]{Paths: piecepaths},
} }
} }
@@ -47,84 +53,98 @@ func (l *Library) Serialize() {
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Agents.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Places.Paths {
l.Agents = nil lwg.Add(1)
go l.Places.Serialize(NewPlaceRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Places.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Agents.Paths {
l.Places = nil lwg.Add(1)
go l.Agents.Serialize(NewAgentRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Works.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Categories.Paths {
l.Works = nil lwg.Add(1)
go l.Categories.Serialize(NewCategoryRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Categories.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Works.Paths {
l.Categories = nil lwg.Add(1)
go l.Works.Serialize(NewWorkRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Issues.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Issues.Paths {
l.Issues = nil lwg.Add(1)
go l.Issues.Serialize(NewIssueRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
go func() { go func() {
defer wg.Done() defer wg.Done()
err := l.Pieces.Serialize() lwg := sync.WaitGroup{}
if err != nil { for _, path := range l.Pieces.Paths {
l.Pieces = nil lwg.Add(1)
go l.Pieces.Serialize(NewPieceRoot(), path, &lwg)
} }
lwg.Wait()
}() }()
wg.Wait() wg.Wait()
} }
// TODO: make Items into a sync.Map func (p *XMLProvider[T]) Serialize(dataholder XMLRootElement[T], path string, wg *sync.WaitGroup) error {
func (p *XMLProvider[T]) Serialize() error {
// Introduce goroutine for every path, locking on append: // Introduce goroutine for every path, locking on append:
var wg sync.WaitGroup if err := UnmarshalFile(path, dataholder); err != nil {
for _, path := range p.paths { logging.Error(err, "Could not unmarshal file: "+path)
wg.Add(1) return err
go func(path string) {
defer wg.Done()
var data T
if err := UnmarshalFile(path, &data); err != nil {
return
} }
p.mu.Lock() for _, item := range dataholder.Children() {
defer p.mu.Unlock() // INFO: Mostly it's just one ID, so the double loop is not that bad.
p.Items = p.Items.Append(data) for _, id := range item.GetIDs() {
}(path) p.Items.Store(id, item)
}
}
if wg != nil {
wg.Done()
} }
wg.Wait()
return nil return nil
} }
func (a *XMLProvider[T]) String() string { func (a *XMLProvider[T]) String() string {
a.mu.Lock() var s string
defer a.mu.Unlock() a.Items.Range(func(key, value interface{}) bool {
return fmt.Sprintf("Items: %s", a.Items) v := value.(T)
s += v.String()
return true
})
return s
} }
func UnmarshalFile[T any](filename string, data *T) error { func UnmarshalFile[T any](filename string, data T) error {
xmlFile, err := os.Open(filename) xmlFile, err := os.Open(filename)
if err != nil { if err != nil {
logging.Error(err, "Could not open file: "+filename) logging.Error(err, "Could not open file: "+filename)
@@ -138,7 +158,7 @@ func UnmarshalFile[T any](filename string, data *T) error {
logging.Error(err, "Could not read file: "+filename) logging.Error(err, "Could not read file: "+filename)
return err return err
} }
err = xml.Unmarshal(byteValue, data) err = xml.Unmarshal(byteValue, &data)
if err != nil { if err != nil {
logging.Error(err, "Could not unmarshal file: "+filename) logging.Error(err, "Could not unmarshal file: "+filename)
@@ -146,3 +166,44 @@ func UnmarshalFile[T any](filename string, data *T) error {
} }
return nil return nil
} }
func (p *XMLProvider[T]) Item(id string) *T {
item, ok := p.Items.Load(id)
if !ok {
return nil
}
i := item.(T)
return &i
}
func (p *XMLProvider[T]) Find(fn func(T) bool) []T {
var items []T
p.Items.Range(func(key, value interface{}) bool {
if fn(value.(T)) {
items = append(items, value.(T))
}
return true
})
return items
}
func (p *XMLProvider[T]) FindKey(fn func(string) bool) []T {
var items []T
p.Items.Range(func(key, value interface{}) bool {
if fn(key.(string)) {
items = append(items, value.(T))
}
return true
})
return items
}
func (p *XMLProvider[T]) All() []T {
var items []T
p.Items.Range(func(key, value interface{}) bool {
items = append(items, value.(T))
return true
})
return items
}

View File

@@ -45,22 +45,23 @@ type Server struct {
running chan bool running chan bool
shutdown *sync.WaitGroup shutdown *sync.WaitGroup
cache *memory.Storage cache *memory.Storage
engine *templating.Engine
mu sync.Mutex mu sync.Mutex
watcher *helpers.FileWatcher watcher *helpers.FileWatcher
kgpz *app.KGPZ kgpz *app.KGPZ
} }
func Create(k *app.KGPZ, c *providers.ConfigProvider) *Server { func Create(k *app.KGPZ, c *providers.ConfigProvider, e *templating.Engine) *Server {
if c == nil || k == nil { if c == nil || k == nil {
logging.Error(nil, "Config or KGPZ is posssibly nil while tying to create server") logging.Error(nil, "Error creating server: Config or App is posssibly nil.")
return nil return nil
} }
return &Server{ return &Server{
Config: c, Config: c,
kgpz: k, kgpz: k,
engine: e,
} }
} }
@@ -96,12 +97,12 @@ func (s *Server) Watcher() error {
} }
func (s *Server) Start() { func (s *Server) Start() {
s.engine.Reload()
s.cache = memory.New(memory.Config{ s.cache = memory.New(memory.Config{
GCInterval: 30 * time.Second, GCInterval: 30 * time.Second,
}) })
engine := templating.NewEngine(&views.LayoutFS, &views.RoutesFS)
srv := fiber.New(fiber.Config{ srv := fiber.New(fiber.Config{
AppName: s.Config.Address, AppName: s.Config.Address,
CaseSensitive: false, CaseSensitive: false,
@@ -118,7 +119,7 @@ func (s *Server) Start() {
PassLocalsToViews: true, PassLocalsToViews: true,
Views: engine, Views: s.engine,
ViewsLayout: templating.DEFAULT_LAYOUT_NAME, ViewsLayout: templating.DEFAULT_LAYOUT_NAME,
}) })

View File

@@ -92,13 +92,13 @@ func (c *TemplateContext) Globals() map[string]string {
return c.globals return c.globals
} }
func (c *TemplateContext) Template(fsys fs.FS) (*template.Template, error) { func (c *TemplateContext) Template(fsys fs.FS, funcmap *template.FuncMap) (*template.Template, error) {
t, err := readTemplates(fsys, nil, c.globals) t, err := readTemplates(fsys, nil, c.globals, funcmap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, err = readTemplates(fsys, t, c.locals) t, err = readTemplates(fsys, t, c.locals, funcmap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -106,14 +106,20 @@ func (c *TemplateContext) Template(fsys fs.FS) (*template.Template, error) {
return t, nil return t, nil
} }
func readTemplates(fsys fs.FS, t *template.Template, paths map[string]string) (*template.Template, error) { func readTemplates(fsys fs.FS, t *template.Template, paths map[string]string, funcmap *template.FuncMap) (*template.Template, error) {
for k, v := range paths { for k, v := range paths {
text, err := fs.ReadFile(fsys, v) text, err := fs.ReadFile(fsys, v)
if err != nil { if err != nil {
return nil, NewError(FileAccessError, v) return nil, NewError(FileAccessError, v)
} }
temp, err := template.New(k).Parse(string(text)) temp := template.New(k)
if funcmap != nil {
temp.Funcs(*funcmap)
}
temp, err = temp.Parse(string(text))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -5,6 +5,9 @@ import (
"io" "io"
"io/fs" "io/fs"
"sync" "sync"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/app"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/functions"
) )
type Engine struct { type Engine struct {
@@ -16,13 +19,28 @@ type Engine struct {
FuncMap template.FuncMap FuncMap template.FuncMap
} }
func NewEngine(layouts, templates *fs.FS) *Engine { // INFO: We pass the app here to be able to access the config and other data for functions
return &Engine{ // which also means we must reload the engine if the app changes
func NewEngine(layouts, templates *fs.FS, app *app.KGPZ) *Engine {
e := Engine{
mu: &sync.Mutex{}, mu: &sync.Mutex{},
LayoutRegistry: NewLayoutRegistry(*layouts), LayoutRegistry: NewLayoutRegistry(*layouts),
TemplateRegistry: NewTemplateRegistry(*templates), TemplateRegistry: NewTemplateRegistry(*templates),
FuncMap: template.FuncMap{},
} }
e.MapFuncs(app)
return &e
}
func (e *Engine) MapFuncs(app *app.KGPZ) error {
e.mu.Lock()
e.FuncMap = make(map[string]interface{})
e.mu.Unlock()
e.AddFunc("GetDate", functions.GetDate)
e.AddFunc("MonthName", functions.MonthName)
e.AddFunc("MonthNameShort", functions.MonthNameShort)
return nil
} }
func (e *Engine) Load() error { func (e *Engine) Load() error {
@@ -43,18 +61,44 @@ func (e *Engine) Load() error {
return nil return nil
} }
func (e *Engine) Reload() error {
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
e.LayoutRegistry.Reset()
}()
go func() {
defer wg.Done()
e.TemplateRegistry.Reset()
}()
wg.Wait()
return nil
}
// INFO: fn is a function that returns either one value or two values, the second one being an error
func (e *Engine) AddFunc(name string, fn interface{}) {
e.mu.Lock()
defer e.mu.Unlock()
e.FuncMap[name] = fn
}
func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error { func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...string) error {
// TODO: check if a reload is needed if files on disk have changed // TODO: check if a reload is needed if files on disk have changed
e.mu.Lock()
defer e.mu.Unlock()
var l *template.Template var l *template.Template
if layout == nil || len(layout) == 0 { if layout == nil || len(layout) == 0 {
lay, err := e.LayoutRegistry.Default() lay, err := e.LayoutRegistry.Default(&e.FuncMap)
if err != nil { if err != nil {
return err return err
} }
l = lay l = lay
} else { } else {
lay, err := e.LayoutRegistry.Layout(layout[0]) lay, err := e.LayoutRegistry.Layout(layout[0], &e.FuncMap)
if err != nil { if err != nil {
return err return err
} }
@@ -66,7 +110,7 @@ func (e *Engine) Render(out io.Writer, path string, data interface{}, layout ...
return err return err
} }
err = e.TemplateRegistry.Add(path, lay) err = e.TemplateRegistry.Add(path, lay, &e.FuncMap)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -33,6 +33,12 @@ func (r *LayoutRegistry) Register(fs fs.FS) *LayoutRegistry {
return NewLayoutRegistry(merged_fs.MergeMultiple(fs, r.layoutsFS)) return NewLayoutRegistry(merged_fs.MergeMultiple(fs, r.layoutsFS))
} }
func (r *LayoutRegistry) Reset() error {
r.cache.Clear()
r.once = sync.Once{}
return nil
}
func (r *LayoutRegistry) Load() error { func (r *LayoutRegistry) Load() error {
r.once.Do(func() { r.once.Do(func() {
err := r.load() err := r.load()
@@ -74,7 +80,7 @@ func (r *LayoutRegistry) load() error {
return nil return nil
} }
func (r *LayoutRegistry) Layout(name string) (*template.Template, error) { func (r *LayoutRegistry) Layout(name string, funcmap *template.FuncMap) (*template.Template, error) {
cached, ok := r.cache.Load(name) cached, ok := r.cache.Load(name)
if ok { if ok {
return cached.(*template.Template), nil return cached.(*template.Template), nil
@@ -87,7 +93,7 @@ func (r *LayoutRegistry) Layout(name string) (*template.Template, error) {
return nil, NewError(NoTemplateError, name) return nil, NewError(NoTemplateError, name)
} }
t, err := context.Template(r.layoutsFS) t, err := context.Template(r.layoutsFS, funcmap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,6 +103,6 @@ func (r *LayoutRegistry) Layout(name string) (*template.Template, error) {
return t, nil return t, nil
} }
func (r *LayoutRegistry) Default() (*template.Template, error) { func (r *LayoutRegistry) Default(funcmap *template.FuncMap) (*template.Template, error) {
return r.Layout(DEFAULT_LAYOUT_NAME) return r.Layout(DEFAULT_LAYOUT_NAME, funcmap)
} }

View File

@@ -33,6 +33,12 @@ func (r *TemplateRegistry) Register(path string, fs fs.FS) *TemplateRegistry {
return NewTemplateRegistry(merged_fs.MergeMultiple(fs, r.routesFS)) return NewTemplateRegistry(merged_fs.MergeMultiple(fs, r.routesFS))
} }
func (r *TemplateRegistry) Reset() error {
r.cache.Clear()
r.once = sync.Once{}
return nil
}
func (r *TemplateRegistry) Load() error { func (r *TemplateRegistry) Load() error {
r.once.Do(func() { r.once.Do(func() {
err := r.load() err := r.load()
@@ -79,7 +85,7 @@ func (r *TemplateRegistry) load() error {
// This function takes a template (typically a layout) and adds all the templates of // This function takes a template (typically a layout) and adds all the templates of
// a given directory path to it. This is useful for adding a layout to a template. // a given directory path to it. This is useful for adding a layout to a template.
func (r *TemplateRegistry) Add(path string, t *template.Template) error { func (r *TemplateRegistry) Add(path string, t *template.Template, funcmap *template.FuncMap) error {
temp, ok := r.cache.Load(path) temp, ok := r.cache.Load(path)
if !ok { if !ok {
r.Load() r.Load()
@@ -88,14 +94,14 @@ func (r *TemplateRegistry) Add(path string, t *template.Template) error {
return NewError(NoTemplateError, path) return NewError(NoTemplateError, path)
} }
template, err := tc.Template(r.routesFS) template, err := tc.Template(r.routesFS, funcmap)
if err != nil { if err != nil {
return err return err
} }
r.cache.Store(path, template) r.cache.Store(path, template)
return r.Add(path, t) return r.Add(path, t, funcmap)
} }
casted := temp.(*template.Template) casted := temp.(*template.Template)

View File

@@ -2,7 +2,6 @@ package viewmodels
import ( import (
"errors" "errors"
"strconv"
"time" "time"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
@@ -10,56 +9,22 @@ import (
const TLAYOUT = "2006-01-02" const TLAYOUT = "2006-01-02"
var TRANSLM = map[string][]string{
"January": {"Januar", "Jan", "1"},
"February": {"Februar", "Feb", "2"},
"March": {"März", "Mär", "3"},
"April": {"April", "Apr", "4"},
"May": {"Mai", "Mai", "5"},
"June": {"Juni", "Jun", "6"},
"July": {"Juli", "Jul", "7"},
"August": {"August", "Aug", "8"},
"September": {"September", "Sep", "9"},
"October": {"Oktober", "Okt", "10"},
"November": {"November", "Nov", "11"},
"December": {"Dezember", "Dez", "12"},
}
var TRANSLD = map[string][]string{
"Monday": {"Montag", "Mo"},
"Tuesday": {"Dienstag", "Di"},
"Wednesday": {"Mittwoch", "Mi"},
"Thursday": {"Donnerstag", "Do"},
"Friday": {"Freitag", "Fr"},
"Saturday": {"Samstag", "Sa"},
"Sunday": {"Sonntag", "So"},
}
type IssueViewModel struct { type IssueViewModel struct {
xmlprovider.Issue xmlprovider.Issue
Weekday []string
Day int Day int
Month []string Month int
Year int
} }
func IssueView(y string, No string, lib *xmlprovider.Library) (*IssueViewModel, error) { func IssueView(y string, No string, lib *xmlprovider.Library) (*IssueViewModel, error) {
n, err := strconv.Atoi(No) issue := lib.Issues.Item(y + "-" + No)
if err != nil {
return nil, err
}
for _, i := range lib.Issues.Items.Issues {
if len(i.Datum.When) < 4 {
continue
}
d := i.Datum.When[:4]
if d == y && i.Number.No == n {
return FromIssue(i)
}
}
if issue == nil {
return nil, errors.New("Issue not found") return nil, errors.New("Issue not found")
}
return FromIssue(*issue)
} }
func FromIssue(i xmlprovider.Issue) (*IssueViewModel, error) { func FromIssue(i xmlprovider.Issue) (*IssueViewModel, error) {
@@ -70,9 +35,10 @@ func FromIssue(i xmlprovider.Issue) (*IssueViewModel, error) {
ivm := IssueViewModel{ ivm := IssueViewModel{
Issue: i, Issue: i,
Weekday: append(TRANSLD[t.Weekday().String()], t.Weekday().String()),
Day: t.Day(), Day: t.Day(),
Month: TRANSLM[t.Month().String()]} Month: int(t.Month()),
Year: t.Year(),
}
return &ivm, nil return &ivm, nil
} }

View File

@@ -1,6 +1,8 @@
package viewmodels package viewmodels
import ( import (
"strconv"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging" "github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider" "github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
) )
@@ -22,8 +24,10 @@ func NewSingleIssueView(y string, No string, lib *xmlprovider.Library) (*SingleI
} }
sivm := SingleIssueViewModel{IssueViewModel: *ivm} sivm := SingleIssueViewModel{IssueViewModel: *ivm}
logging.Info(strconv.Itoa(len(lib.Pieces.All())) + "pieces in library")
for _, a := range lib.Pieces.Items.Piece { lib.Pieces.Items.Range(func(key, value interface{}) bool {
a := value.(xmlprovider.Piece)
for _, r := range a.IssueRefs { for _, r := range a.IssueRefs {
if r.Datum == y && r.Nr == No { if r.Datum == y && r.Nr == No {
p, err := NewPieceView(a) p, err := NewPieceView(a)
@@ -45,7 +49,8 @@ func NewSingleIssueView(y string, No string, lib *xmlprovider.Library) (*SingleI
sivm.Additionals = append(sivm.Additionals, p) sivm.Additionals = append(sivm.Additionals, p)
} }
} }
} return true
})
return &sivm, nil return &sivm, nil
} }

View File

@@ -21,9 +21,11 @@ func YearView(year string, lib *xmlprovider.Library) (*YearViewModel, error) {
res := YearViewModel{Year: year} res := YearViewModel{Year: year}
res.Issues = make(IssuesByMonth, 12) res.Issues = make(IssuesByMonth, 12)
last := "" last := ""
for _, issue := range lib.Issues.Items.Issues {
lib.Issues.Items.Range(func(key, value interface{}) bool {
issue := value.(xmlprovider.Issue)
if len(issue.Datum.When) < 4 { if len(issue.Datum.When) < 4 {
continue return true
} }
date := issue.Datum.When[0:4] date := issue.Datum.When[0:4]
@@ -35,7 +37,8 @@ func YearView(year string, lib *xmlprovider.Library) (*YearViewModel, error) {
if date == year { if date == year {
res.PushIssue(issue) res.PushIssue(issue)
} }
} return true
})
if len(res.Issues) == 0 { if len(res.Issues) == 0 {
return nil, errors.New("No issues found for year " + year) return nil, errors.New("No issues found for year " + year)
@@ -52,14 +55,12 @@ func (y *YearViewModel) PushIssue(i xmlprovider.Issue) {
return return
} }
month, _ := strconv.Atoi(iv.Month[2]) list, ok := y.Issues[iv.Month]
list, ok := y.Issues[month]
if !ok { if !ok {
list = []IssueViewModel{} list = []IssueViewModel{}
} }
y.Issues[month] = append(list, *iv) y.Issues[iv.Month] = append(list, *iv)
} }
func (y *YearViewModel) PushAvailable(s string) { func (y *YearViewModel) PushAvailable(s string) {

View File

@@ -8,18 +8,21 @@
<!-- Month Header --> <!-- Month Header -->
{{ $first := index $month 0 }} {{ $first := index $month 0 }}
<h2>{{ index $first.Month 1 }}</h2> <h2>{{ MonthNameShort $first.Month }}</h2>
<!-- Issues --> <!-- Issues -->
{{ range $issue := $month }} {{ range $issue := $month }}
{{ $date := GetDate $issue.Datum.When }}
<a href="/{{ $y }}/{{ $issue.Number.No }}"> <a href="/{{ $y }}/{{ $issue.Number.No }}">
<div> <div>
{{ $issue.Number.No }} {{ $issue.Number.No }}
</div> </div>
<div> <div>
{{ index $issue.Weekday 1 }} {{ $date.Wd }}
</div> </div>
<div>{{ $issue.Day }}.{{ index $issue.Month 2 }}.</div>
<div>{{ $date.DayNo }}.{{ index $date.MonthNo }}.</div>
</a> </a>
{{ end }} {{ end }}
{{ end }} {{ end }}