mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-12-15 11:35:30 +00:00
added XSDTime datatype
This commit is contained in:
@@ -19,7 +19,7 @@ func GetYear(kgpz *app.KGPZ) fiber.Handler {
|
|||||||
return c.SendStatus(fiber.StatusNotFound)
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
view, err := viewmodels.YearView(y, kgpz.Library)
|
view, err := viewmodels.YearView(yi, kgpz.Library)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.ErrorDebug(err, "Keine Ausgaben für das Jahr "+y)
|
logging.ErrorDebug(err, "Keine Ausgaben für das Jahr "+y)
|
||||||
return c.SendStatus(fiber.StatusNotFound)
|
return c.SendStatus(fiber.StatusNotFound)
|
||||||
|
|||||||
271
helpers/xsdtime/xsdtime.go
Normal file
271
helpers/xsdtime/xsdtime.go
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
package xsdtime
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An implementation of the xsd 1.1 datatypes:
|
||||||
|
// date, gDay, gMonth, gMonthDay, gYear, gYearMonth.
|
||||||
|
|
||||||
|
type XSDDatetype int
|
||||||
|
type Seperator byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEFAULT_YEAR = 0
|
||||||
|
DEFAULT_DAY = 1
|
||||||
|
DEFAULT_MONTH = 1
|
||||||
|
|
||||||
|
MIN_ALLOWED_NUMBER = 0x30 // 0
|
||||||
|
MAX_ALLOWED_NUMBER = 0x39 // 9
|
||||||
|
SIGN = 0x2D // -
|
||||||
|
SEPERATOR = 0x2D // -
|
||||||
|
PLUS = 0x2B // +
|
||||||
|
COLON = 0x3A // :
|
||||||
|
TIMEZONE = 0x5A // Z
|
||||||
|
NONE = 0x00 // 0
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Date XSDDatetype = iota
|
||||||
|
GDay
|
||||||
|
GMonth
|
||||||
|
GYear
|
||||||
|
GMonthDay
|
||||||
|
GYearMonth
|
||||||
|
)
|
||||||
|
|
||||||
|
type XSDDate struct {
|
||||||
|
Year int
|
||||||
|
Month int
|
||||||
|
Day int
|
||||||
|
Timezone int
|
||||||
|
|
||||||
|
Type XSDDatetype
|
||||||
|
HasTimezone bool
|
||||||
|
|
||||||
|
Time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check:
|
||||||
|
// MONTH DAY + Date: Sanity check Month and Day. Additional checks:
|
||||||
|
// - Month: 2 - Day < 30
|
||||||
|
// - Month: 4, 6, 9, 11 - Day < 31
|
||||||
|
// - Month: 1, 3, 5, 7, 8, 10, 12 - Day < 32
|
||||||
|
// YEAR + Date: Sanity check Year + February 29. Check zero padding.
|
||||||
|
// Additional checks:
|
||||||
|
// - Feb 29 on leap years: y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
|
||||||
|
// -> Check last 2 digits: if both are zero, check first two digits.
|
||||||
|
// Else if last digit is n % 4 == 0, the second to last digit m % 2 == 0
|
||||||
|
// Else if last digit is n % 4 == 2, the second to last digit m % 2 == 1
|
||||||
|
// Else its not a leap year.
|
||||||
|
// - no 0000 Year
|
||||||
|
//
|
||||||
|
|
||||||
|
func (d XSDDate) String() string {
|
||||||
|
var s string
|
||||||
|
if d.Year != 0 {
|
||||||
|
s += fmt.Sprintf("%d", d.Year)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Month != 0 {
|
||||||
|
if d.Year == 0 {
|
||||||
|
s += "-"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("-%02d", d.Month)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Day != 0 {
|
||||||
|
if d.Year == 0 && d.Month == 0 {
|
||||||
|
s += "--"
|
||||||
|
}
|
||||||
|
s += fmt.Sprintf("-%02d", d.Day)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.HasTimezone {
|
||||||
|
if d.Timezone == 0 {
|
||||||
|
s += "Z"
|
||||||
|
} else {
|
||||||
|
m := d.Timezone % 60
|
||||||
|
if m < 0 {
|
||||||
|
m *= -1
|
||||||
|
}
|
||||||
|
|
||||||
|
hint := d.Timezone / 60
|
||||||
|
sep := "+"
|
||||||
|
if hint < 0 {
|
||||||
|
sep = "-"
|
||||||
|
hint *= -1
|
||||||
|
}
|
||||||
|
h := fmt.Sprintf("%02d", hint)
|
||||||
|
|
||||||
|
s += fmt.Sprintf("%v%v:%02d", sep, h, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *XSDDate) UnmarshalText(text []byte) error {
|
||||||
|
dt, err := Parse(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.Year = dt.Year
|
||||||
|
d.Month = dt.Month
|
||||||
|
d.Day = dt.Day
|
||||||
|
d.Timezone = dt.Timezone
|
||||||
|
d.Type = dt.Type
|
||||||
|
d.HasTimezone = dt.HasTimezone
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d XSDDate) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(d.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(s string) (XSDDate, error) {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
|
||||||
|
// The smallest possible date is 4 chars long
|
||||||
|
if len(s) < 4 {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid date")
|
||||||
|
}
|
||||||
|
|
||||||
|
y := 0
|
||||||
|
m := 0
|
||||||
|
d := 0
|
||||||
|
|
||||||
|
hastz := false
|
||||||
|
tz := 0
|
||||||
|
|
||||||
|
if len(s) >= 5 && s[len(s)-1] == TIMEZONE {
|
||||||
|
hastz = true
|
||||||
|
tz = 0
|
||||||
|
s = s[:len(s)-1]
|
||||||
|
} else if len(s) >= 10 {
|
||||||
|
t, err := parseTimezone(s[len(s)-6:])
|
||||||
|
if err == nil {
|
||||||
|
hastz = true
|
||||||
|
tz = t
|
||||||
|
s = s[:len(s)-6]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Year
|
||||||
|
if s[1] != SEPERATOR {
|
||||||
|
i := 3
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
if !isAllowed(s[i]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yint, err := strconv.Atoi(s[:i])
|
||||||
|
if err != nil {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid year: %v", s[:i])
|
||||||
|
} else if yint == 0 {
|
||||||
|
return XSDDate{}, fmt.Errorf("Zero is an invalid year")
|
||||||
|
}
|
||||||
|
y = yint
|
||||||
|
|
||||||
|
if i == len(s) {
|
||||||
|
return XSDDate{Year: y, Type: GYear, Timezone: tz, HasTimezone: hastz}, nil
|
||||||
|
} else if i >= len(s)-1 || s[i] != SEPERATOR {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid date ending")
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[i+1:]
|
||||||
|
} else {
|
||||||
|
s = s[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Left are 02 (Month), -02 (Day), 02-02 (Date)
|
||||||
|
if s[0] != SEPERATOR {
|
||||||
|
mstr := s[:2]
|
||||||
|
mint, err := strconv.Atoi(mstr)
|
||||||
|
if err != nil {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid month")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mint < 1 || mint > 12 {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid month value")
|
||||||
|
}
|
||||||
|
|
||||||
|
m = mint
|
||||||
|
s = s[2:]
|
||||||
|
if len(s) == 0 {
|
||||||
|
if y == 0 {
|
||||||
|
return XSDDate{Month: m, Type: GMonth, HasTimezone: hastz, Timezone: tz}, nil
|
||||||
|
} else {
|
||||||
|
return XSDDate{Year: y, Month: m, Type: GYearMonth, HasTimezone: hastz, Timezone: tz}, nil
|
||||||
|
}
|
||||||
|
} else if len(s) != 3 || s[0] != SEPERATOR {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid date ending: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s[1:]
|
||||||
|
|
||||||
|
// Left is 02 Day
|
||||||
|
dint, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid day: %v", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dint < 1 || dint > 31 {
|
||||||
|
return XSDDate{}, fmt.Errorf("Invalid day value: %v", dint)
|
||||||
|
}
|
||||||
|
|
||||||
|
d = dint
|
||||||
|
if y == 0 {
|
||||||
|
if m == 0 {
|
||||||
|
return XSDDate{Day: d, Type: GDay, HasTimezone: hastz, Timezone: tz}, nil
|
||||||
|
} else {
|
||||||
|
return XSDDate{Month: m, Day: d, Type: GMonthDay, HasTimezone: hastz, Timezone: tz}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return XSDDate{Year: y, Month: m, Day: d, Type: Date, HasTimezone: hastz, Timezone: tz}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimezone(s string) (int, error) {
|
||||||
|
// INFO: We assume the check for 'Z' has already been done
|
||||||
|
if len(s) != 6 || s[3] != COLON || (s[0] != PLUS && s[0] != SIGN) {
|
||||||
|
return 0, fmt.Errorf("Invalid timezone")
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := strconv.Atoi(s[:3])
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Invalid hour: %v", s[:3])
|
||||||
|
}
|
||||||
|
|
||||||
|
m, err := strconv.Atoi(s[4:])
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("Invalid minute: %v", s[4:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h < -13 || h > 13) && ((h == -14 || h == 14) && m != 0) {
|
||||||
|
return 0, fmt.Errorf("Invalid timezone: hour: %v minute: %v", h, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m < 0 || m > 59 {
|
||||||
|
return 0, fmt.Errorf("Invalid timezone: minute: %v", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
h *= 60
|
||||||
|
if h < 0 {
|
||||||
|
h -= m
|
||||||
|
} else {
|
||||||
|
h += m
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAllowed(c byte) bool {
|
||||||
|
return c >= MIN_ALLOWED_NUMBER && c <= MAX_ALLOWED_NUMBER
|
||||||
|
}
|
||||||
61
helpers/xsdtime/xsdtime_test.go
Normal file
61
helpers/xsdtime/xsdtime_test.go
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package xsdtime
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type Test struct {
|
||||||
|
Input string
|
||||||
|
Output XSDDate
|
||||||
|
}
|
||||||
|
|
||||||
|
var tests = []Test{
|
||||||
|
{"2006-01-02", XSDDate{Year: 2006, Month: 1, Day: 2, Type: Date}},
|
||||||
|
{"-1222-01-02", XSDDate{Year: -1222, Month: 1, Day: 2, Type: Date}},
|
||||||
|
{"-2777", XSDDate{Year: -2777, Type: GYear}},
|
||||||
|
{"1988-12:30", XSDDate{Year: 1988, Type: GYear, HasTimezone: true, Timezone: (60*12 + 30) * -1}},
|
||||||
|
{"--03+05:00", XSDDate{Month: 3, Type: GMonth, HasTimezone: true, Timezone: 300}},
|
||||||
|
{"---29", XSDDate{Day: 29, Type: GDay}},
|
||||||
|
{"-1234567-12Z", XSDDate{Year: -1234567, Month: 12, Type: GYearMonth, HasTimezone: true, Timezone: 0}},
|
||||||
|
{"-1234567-12+05:00", XSDDate{Year: -1234567, Month: 12, Type: GYearMonth, HasTimezone: true, Timezone: 300}},
|
||||||
|
{"--12-31", XSDDate{Month: 12, Day: 31, Type: GMonthDay}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
for _, test := range tests {
|
||||||
|
dt, err := Parse(test.Input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing %v: %v", test.Input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dt.Type != test.Output.Type {
|
||||||
|
t.Errorf("Type mismatch for %v: expected %v, got %v", test.Input, test.Output.Type, dt.Type)
|
||||||
|
}
|
||||||
|
if dt.Year != test.Output.Year {
|
||||||
|
t.Errorf("Year mismatch for %v: expected %v, got %v", test.Input, test.Output.Year, dt.Year)
|
||||||
|
}
|
||||||
|
if dt.Month != test.Output.Month {
|
||||||
|
t.Errorf("Month mismatch for %v: expected %v, got %v", test.Input, test.Output.Month, dt.Month)
|
||||||
|
}
|
||||||
|
if dt.Day != test.Output.Day {
|
||||||
|
t.Errorf("Day mismatch for %v: expected %v, got %v", test.Input, test.Output.Day, dt.Day)
|
||||||
|
}
|
||||||
|
if dt.HasTimezone != test.Output.HasTimezone {
|
||||||
|
t.Errorf("Timezone mismatch for %v: expected %v, got %v", test.Input, test.Output.HasTimezone, dt.HasTimezone)
|
||||||
|
}
|
||||||
|
if dt.Timezone != test.Output.Timezone {
|
||||||
|
t.Errorf("Timezone mismatch for %v: expected %v, got %v", test.Input, test.Output.Timezone, dt.Timezone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
for _, test := range tests {
|
||||||
|
dt, err := Parse(test.Input)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error parsing %v: %v", test.Input, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if dt.String() != test.Input {
|
||||||
|
t.Errorf("String mismatch for %v: expected %v, got %v", test.Input, test.Input, dt.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,8 +40,8 @@ func (i Issue) Keys() []string {
|
|||||||
res = append(res, date)
|
res = append(res, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(date) > 4 {
|
if ref, err := i.Reference(); err == nil {
|
||||||
res = append(res, i.Datum.When[0:4]+"-"+strconv.Itoa(i.Number.No))
|
res = append(res, ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
i.keys = res
|
i.keys = res
|
||||||
@@ -49,6 +49,22 @@ func (i Issue) Keys() []string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: We could even cache this
|
||||||
|
func (i Issue) Year() (int, error) {
|
||||||
|
if date := i.Datum.Date(); date != nil {
|
||||||
|
return date.Year(), nil
|
||||||
|
}
|
||||||
|
return 0, InvalidDateError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Issue) Reference() (string, error) {
|
||||||
|
if date := i.Datum.Date(); date != nil {
|
||||||
|
return strconv.Itoa(date.Year()) + "-" + strconv.Itoa(i.Number.No), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", InvalidDateError
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,22 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Piece struct {
|
type Piece struct {
|
||||||
XMLName xml.Name `xml:"beitrag"`
|
XMLName xml.Name `xml:"beitrag"`
|
||||||
IssueRefs []IssueRef `xml:"stueck"`
|
IssueRefs []IssueRef `xml:"stueck"`
|
||||||
PlaceRefs []PlaceRef `xml:"ort"`
|
PlaceRefs []PlaceRef `xml:"ort"`
|
||||||
CategoryRefs []CategoryRef `xml:"kategorie"`
|
CategoryRefs []CategoryRef `xml:"kategorie"`
|
||||||
AgentRefs []AgentRef `xml:"akteur"`
|
AgentRefs []AgentRef `xml:"akteur"`
|
||||||
WorkRefs []WorkRef `xml:"werk"`
|
WorkRefs []WorkRef `xml:"werk"`
|
||||||
PieceRefs []PieceRef `xml:"beitrag"`
|
PieceRefs []PieceRef `xml:"beitrag"`
|
||||||
AdditionalRef []AdditionalRef `xml:"beilage"`
|
Datum []KGPZDate `xml:"datum"`
|
||||||
Datum []KGPZDate `xml:"datum"`
|
Incipit []string `xml:"incipit"`
|
||||||
Incipit []string `xml:"incipit"`
|
Title []string `xml:"titel"`
|
||||||
Title []string `xml:"titel"`
|
|
||||||
Identifier
|
Identifier
|
||||||
AnnotationNote
|
AnnotationNote
|
||||||
}
|
}
|
||||||
|
|
||||||
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\nIncipit: %v\nTitle: %v\nAnnotations: %v\nNotes: %v\n", p.ID, p.IssueRefs, p.PlaceRefs, p.CategoryRefs, p.AgentRefs, p.WorkRefs, p.PieceRefs, p.Incipit, p.Title, p.Annotations, p.Notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Piece) Keys() []string {
|
func (p Piece) Keys() []string {
|
||||||
@@ -42,11 +41,9 @@ func (p Piece) Keys() []string {
|
|||||||
uid := uuid.New()
|
uid := uuid.New()
|
||||||
|
|
||||||
for _, i := range p.IssueRefs {
|
for _, i := range p.IssueRefs {
|
||||||
ret = append(ret, i.Datum+"-"+strconv.Itoa(i.Nr)+"-"+uid.String())
|
if d := i.Date(); d != nil {
|
||||||
}
|
ret = append(ret, strconv.Itoa(d.Year())+"-"+strconv.Itoa(i.Nr)+"-"+uid.String())
|
||||||
|
}
|
||||||
for _, i := range p.AdditionalRef {
|
|
||||||
ret = append(ret, i.Datum+"-"+strconv.Itoa(i.Nr)+"-b-"+uid.String())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.keys = ret
|
p.keys = ret
|
||||||
@@ -54,6 +51,19 @@ func (p Piece) Keys() []string {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p Piece) ReferencesIssue(y, no int) (*IssueRef, bool) {
|
||||||
|
for _, i := range p.IssueRefs {
|
||||||
|
if i.Nr == no {
|
||||||
|
d := i.Date()
|
||||||
|
if d != nil && d.Year() == y {
|
||||||
|
return &i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: We can make this fast depending on which category to look for
|
// TODO: We can make this fast depending on which category to look for
|
||||||
// but we'll have to define rules for every single category (~35 of them)
|
// but we'll have to define rules for every single category (~35 of them)
|
||||||
func (p Piece) IsCat(k string) bool {
|
func (p Piece) IsCat(k string) bool {
|
||||||
|
|||||||
@@ -1,17 +1,31 @@
|
|||||||
package xmlprovider
|
package xmlprovider
|
||||||
|
|
||||||
import "encoding/xml"
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/xsdtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var InvalidDateError = errors.New("Invalid date")
|
||||||
|
|
||||||
|
const DateLayout = "2006-01-02"
|
||||||
|
|
||||||
type KGPZDate struct {
|
type KGPZDate struct {
|
||||||
XMLName xml.Name `xml:"datum"`
|
XMLName xml.Name `xml:"datum"`
|
||||||
When string `xml:"when,attr"`
|
DateAttributes
|
||||||
NotBefore string `xml:"notBefore,attr"`
|
|
||||||
NotAfter string `xml:"notAfter,attr"`
|
|
||||||
From string `xml:"from,attr"`
|
|
||||||
To string `xml:"to,attr"`
|
|
||||||
Value
|
Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DateAttributes struct {
|
||||||
|
When xsdtime.XSDDate `xml:"when,attr"`
|
||||||
|
NotBefore xsdtime.XSDDate `xml:"notBefore,attr"`
|
||||||
|
NotAfter xsdtime.XSDDate `xml:"notAfter,attr"`
|
||||||
|
From xsdtime.XSDDate `xml:"from,attr"`
|
||||||
|
To xsdtime.XSDDate `xml:"to,attr"`
|
||||||
|
Cert string `xml:"cert,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
type URL struct {
|
type URL struct {
|
||||||
XMLName xml.Name `xml:"url"`
|
XMLName xml.Name `xml:"url"`
|
||||||
Address string `xml:"address,attr"`
|
Address string `xml:"address,attr"`
|
||||||
|
|||||||
@@ -8,22 +8,23 @@ type AgentRef struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AdditionalRef struct {
|
type AdditionalRef struct {
|
||||||
XMLName xml.Name `xml:"beilage"`
|
XMLName xml.Name `xml:"beilage"`
|
||||||
Reference
|
Reference // Ist nicht im Schema
|
||||||
Datum string `xml:"datum,attr"`
|
Datum string `xml:"datum,attr"`
|
||||||
Nr int `xml:"nr,attr"`
|
Nr int `xml:"nr,attr"`
|
||||||
AdditionalNo int `xml:"beilage,attr"`
|
AdditionalNo int `xml:"beilage,attr"`
|
||||||
Von int `xml:"von,attr"`
|
Von int `xml:"von,attr"`
|
||||||
Bis int `xml:"bis,attr"`
|
Bis int `xml:"bis,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssueRef struct {
|
type IssueRef struct {
|
||||||
XMLName xml.Name `xml:"stueck"`
|
XMLName xml.Name `xml:"stueck"`
|
||||||
Reference
|
Reference // Ist nicht im Schema
|
||||||
Datum string `xml:"datum,attr"`
|
DateAttributes
|
||||||
Nr int `xml:"nr,attr"`
|
Nr int `xml:"nr,attr"`
|
||||||
Von int `xml:"von,attr"`
|
Von int `xml:"von,attr"`
|
||||||
Bis int `xml:"bis,attr"`
|
Bis int `xml:"bis,attr"`
|
||||||
|
Beilage int `xml:"beilage,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PlaceRef struct {
|
type PlaceRef struct {
|
||||||
|
|||||||
@@ -1,44 +1,84 @@
|
|||||||
package viewmodels
|
package viewmodels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"time"
|
"log/slog"
|
||||||
|
"maps"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/functions"
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TLAYOUT = "2006-01-02"
|
type PieceListitemVM struct {
|
||||||
|
xmlprovider.Piece
|
||||||
type IssueViewModel struct {
|
// TODO: this is a bit hacky, but it refences the page number of the piece in the issue
|
||||||
*xmlprovider.Issue
|
Reference xmlprovider.IssueRef
|
||||||
Day int
|
|
||||||
Month int
|
|
||||||
Year int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssueView(y string, No string, lib *xmlprovider.Library) (*IssueViewModel, error) {
|
type PiecesByPage struct {
|
||||||
issue := lib.Issues.Item(y + "-" + No)
|
Items map[int][]PieceListitemVM
|
||||||
|
Pages []int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Next & Prev
|
||||||
|
type IssueVM struct {
|
||||||
|
IssueListitemVM
|
||||||
|
Pieces PiecesByPage
|
||||||
|
AdditionalPieces PiecesByPage
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSingleIssueView(y string, no string, lib *xmlprovider.Library) (*IssueVM, error) {
|
||||||
|
issue := lib.Issues.Item(y + "-" + no)
|
||||||
if issue == nil {
|
if issue == nil {
|
||||||
return nil, errors.New("Issue not found")
|
return nil, fmt.Errorf("No issue found for %v-%v", y, no)
|
||||||
}
|
}
|
||||||
|
|
||||||
return FromIssue(issue)
|
ivm, err := ListitemFromIssue(*issue)
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromIssue(i *xmlprovider.Issue) (*IssueViewModel, error) {
|
|
||||||
t, err := time.Parse(TLAYOUT, i.Datum.When)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ivm := IssueViewModel{
|
sivm := IssueVM{IssueListitemVM: *ivm}
|
||||||
Issue: i,
|
|
||||||
Day: t.Day(),
|
ppi, ppa, err := PiecesForIsssue(lib, *issue)
|
||||||
Month: int(t.Month()),
|
|
||||||
Year: t.Year(),
|
slices.Sort(ppi.Pages)
|
||||||
|
slices.Sort(ppa.Pages)
|
||||||
|
|
||||||
|
sivm.Pieces = *ppi
|
||||||
|
sivm.AdditionalPieces = *ppa
|
||||||
|
|
||||||
|
return &sivm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func PiecesForIsssue(lib *xmlprovider.Library, issue xmlprovider.Issue) (*PiecesByPage, *PiecesByPage, error) {
|
||||||
|
date := issue.Datum.Date()
|
||||||
|
if date == nil {
|
||||||
|
return nil, nil, fmt.Errorf("Issue has no date")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ivm, nil
|
year := date.Year()
|
||||||
|
|
||||||
|
ppi := PiecesByPage{Items: make(map[int][]PieceListitemVM)}
|
||||||
|
ppa := PiecesByPage{Items: make(map[int][]PieceListitemVM)}
|
||||||
|
|
||||||
|
slog.Debug(fmt.Sprintf("Checking piece for year %v, number %v", year, issue.Number.No))
|
||||||
|
for _, piece := range lib.Pieces.Array {
|
||||||
|
|
||||||
|
if d, ok := piece.ReferencesIssue(year, issue.Number.No); ok {
|
||||||
|
slog.Debug(fmt.Sprintf("Found piece %v in issue %v-%v", piece, year, issue.Number.No))
|
||||||
|
p := PieceListitemVM{Piece: piece, Reference: *d}
|
||||||
|
if d.Beilage > 0 {
|
||||||
|
functions.MapArrayInsert(ppa.Items, d.Von, p)
|
||||||
|
} else {
|
||||||
|
functions.MapArrayInsert(ppi.Items, d.Von, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ppi.Pages = slices.Collect(maps.Keys(ppi.Items))
|
||||||
|
ppa.Pages = slices.Collect(maps.Keys(ppa.Items))
|
||||||
|
|
||||||
|
return &ppi, &ppa, nil
|
||||||
}
|
}
|
||||||
|
|||||||
32
viewmodels/issuelistitem_view.go
Normal file
32
viewmodels/issuelistitem_view.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package viewmodels
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TLAYOUT = "2006-01-02"
|
||||||
|
|
||||||
|
type IssueListitemVM struct {
|
||||||
|
xmlprovider.Issue
|
||||||
|
No int
|
||||||
|
Day int
|
||||||
|
Month int
|
||||||
|
Year int
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListitemFromIssue(i xmlprovider.Issue) (*IssueListitemVM, error) {
|
||||||
|
t, err := time.Parse(TLAYOUT, i.Datum.When)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IssueListitemVM{
|
||||||
|
No: i.Number.No,
|
||||||
|
Issue: i,
|
||||||
|
Day: t.Day(),
|
||||||
|
Month: int(t.Month()),
|
||||||
|
Year: t.Year(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package viewmodels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PieceViewModel struct {
|
|
||||||
*xmlprovider.Piece
|
|
||||||
// TODO: this is a bit hacky, but it refences the page number of the piece in the issue
|
|
||||||
Von int
|
|
||||||
Bis int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPieceView(p *xmlprovider.Piece) (PieceViewModel, error) {
|
|
||||||
return PieceViewModel{Piece: p}, nil
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
package viewmodels
|
|
||||||
|
|
||||||
import (
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/functions"
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/helpers/logging"
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SingleIssueViewModel struct {
|
|
||||||
IssueViewModel
|
|
||||||
No int
|
|
||||||
Year string
|
|
||||||
Pieces map[int][]PieceViewModel
|
|
||||||
Pages []int
|
|
||||||
AdditionalPieces map[int][]PieceViewModel
|
|
||||||
AdditionalPages []int
|
|
||||||
Next IssueViewModel
|
|
||||||
Prev IssueViewModel
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSingleIssueView(y string, No string, lib *xmlprovider.Library) (*SingleIssueViewModel, error) {
|
|
||||||
ivm, err := IssueView(y, No, lib)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
no, err := strconv.Atoi(No)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sivm := SingleIssueViewModel{IssueViewModel: *ivm, No: no, Year: y}
|
|
||||||
|
|
||||||
if err := sivm.PiecesForIsssue(lib); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.Sort(sivm.Pages)
|
|
||||||
slices.Sort(sivm.AdditionalPages)
|
|
||||||
|
|
||||||
return &sivm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (issue *SingleIssueViewModel) PiecesForIsssue(lib *xmlprovider.Library) error {
|
|
||||||
nostr := strconv.Itoa(issue.No)
|
|
||||||
lookfor := issue.Year + "-" + nostr + "-"
|
|
||||||
n := issue.No
|
|
||||||
y := issue.Year
|
|
||||||
|
|
||||||
adp := make(map[int][]PieceViewModel)
|
|
||||||
ip := make(map[int][]PieceViewModel)
|
|
||||||
|
|
||||||
lib.Pieces.Items.Range(func(key, value interface{}) bool {
|
|
||||||
k := key.(string)
|
|
||||||
if strings.HasPrefix(k, lookfor) {
|
|
||||||
a := value.(*xmlprovider.Piece)
|
|
||||||
p, err := NewPieceView(a)
|
|
||||||
if err != nil {
|
|
||||||
logging.ObjErr(&a, err)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(k, lookfor+"b-") {
|
|
||||||
for _, i := range p.AdditionalRef {
|
|
||||||
// INFO: Here we find the page number the piece has in THIS issue, same below
|
|
||||||
if i.Datum == y && i.Nr == n {
|
|
||||||
p.Von = i.Von
|
|
||||||
p.Bis = i.Bis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
functions.MapArrayInsert(adp, p.Von, p)
|
|
||||||
} else {
|
|
||||||
for _, i := range p.IssueRefs {
|
|
||||||
if i.Datum == y && i.Nr == n {
|
|
||||||
p.Von = i.Von
|
|
||||||
p.Bis = i.Bis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
functions.MapArrayInsert(ip, p.Von, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
issue.Pieces = ip
|
|
||||||
issue.Pages = functions.Keys(ip)
|
|
||||||
|
|
||||||
issue.AdditionalPieces = adp
|
|
||||||
issue.AdditionalPages = functions.Keys(adp)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,100 +1,15 @@
|
|||||||
package viewmodels
|
package viewmodels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
"github.com/Theodor-Springmann-Stiftung/kgpz_web/providers/xmlprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IssuesByMonth map[int][]IssueViewModel
|
type IssuesByMonth map[int][]IssueListitemVM
|
||||||
|
|
||||||
type YearViewModel struct {
|
|
||||||
Year string
|
|
||||||
AvailableYears []string
|
|
||||||
Issues IssuesByMonth
|
|
||||||
}
|
|
||||||
|
|
||||||
func YearView(year string, lib *xmlprovider.Library) (*YearViewModel, error) {
|
|
||||||
res := YearViewModel{Year: year}
|
|
||||||
res.Issues = make(IssuesByMonth, 12)
|
|
||||||
last := ""
|
|
||||||
|
|
||||||
lib.Issues.Items.Range(func(key, value interface{}) bool {
|
|
||||||
k := key.(string)
|
|
||||||
if len(k) < 4 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
date := k[0:4]
|
|
||||||
if date != last {
|
|
||||||
res.PushAvailable(date)
|
|
||||||
last = date
|
|
||||||
}
|
|
||||||
|
|
||||||
if date == year {
|
|
||||||
issue := value.(*xmlprovider.Issue)
|
|
||||||
res.PushIssue(issue)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(res.Issues) == 0 {
|
|
||||||
return nil, errors.New("No issues found for year " + year)
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Sort()
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *YearViewModel) Sort() {
|
|
||||||
y.SortAvailableYears()
|
|
||||||
y.Issues.Sort()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *YearViewModel) PushIssue(i *xmlprovider.Issue) {
|
|
||||||
iv, err := FromIssue(i)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
list, ok := y.Issues[iv.Month]
|
|
||||||
if !ok {
|
|
||||||
list = []IssueViewModel{}
|
|
||||||
} else {
|
|
||||||
for _, issue := range list {
|
|
||||||
if issue.Number.No == iv.Number.No {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
y.Issues[iv.Month] = append(list, *iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *YearViewModel) PushAvailable(s string) {
|
|
||||||
|
|
||||||
if !slices.Contains(y.AvailableYears, s) {
|
|
||||||
y.AvailableYears = append(y.AvailableYears, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (y *YearViewModel) SortAvailableYears() {
|
|
||||||
sort.Slice(y.AvailableYears, func(i, j int) bool {
|
|
||||||
iint, err := strconv.Atoi(y.AvailableYears[i])
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
jint, err := strconv.Atoi(y.AvailableYears[j])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return iint < jint
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ibm *IssuesByMonth) Sort() {
|
func (ibm *IssuesByMonth) Sort() {
|
||||||
for _, issues := range *ibm {
|
for _, issues := range *ibm {
|
||||||
@@ -103,3 +18,41 @@ func (ibm *IssuesByMonth) Sort() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type YearVM struct {
|
||||||
|
Year int
|
||||||
|
AvailableYears []int
|
||||||
|
Issues IssuesByMonth
|
||||||
|
}
|
||||||
|
|
||||||
|
func YearView(year int, lib *xmlprovider.Library) (*YearVM, error) {
|
||||||
|
issues := make(IssuesByMonth, 12)
|
||||||
|
years := make(map[int]bool)
|
||||||
|
|
||||||
|
lib.Issues.Lock()
|
||||||
|
for _, issue := range lib.Issues.Array {
|
||||||
|
if y, err := issue.Year(); err == nil {
|
||||||
|
years[y] = true
|
||||||
|
if y == year {
|
||||||
|
if issuevm, err := ListitemFromIssue(issue); err == nil {
|
||||||
|
issues[issuevm.Month] = append(issues[issuevm.Month], *issuevm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lib.Issues.Unlock()
|
||||||
|
|
||||||
|
if len(issues) == 0 {
|
||||||
|
return nil, fmt.Errorf("No issues found for year %v", year)
|
||||||
|
}
|
||||||
|
|
||||||
|
availableyears := slices.Collect(maps.Keys(years))
|
||||||
|
slices.Sort(availableyears)
|
||||||
|
issues.Sort()
|
||||||
|
|
||||||
|
return &YearVM{
|
||||||
|
Year: year,
|
||||||
|
AvailableYears: availableyears,
|
||||||
|
Issues: issues,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
|
|
||||||
<div>Inhalt</div>
|
<div>Inhalt</div>
|
||||||
<div>
|
<div>
|
||||||
{{ range $page := $model.Pages }}
|
{{ range $page := $model.Pieces.Pages }}
|
||||||
<div>
|
<div>
|
||||||
<div>Seite {{ $page }}</div>
|
<div>Seite {{ $page }}</div>
|
||||||
|
|
||||||
{{ range $piece := (index $model.Pieces $page) }}
|
{{ range $piece := (index $model.Pieces.Items $page) }}
|
||||||
|
|
||||||
{{ template "_inhaltsverzeichnis_eintrag" $piece }}
|
{{ template "_inhaltsverzeichnis_eintrag" $piece }}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user