package functions import ( "math/rand" "strconv" "strings" "github.com/Theodor-Springmann-Stiftung/lenz-web/xmlmodels" "github.com/Theodor-Springmann-Stiftung/lenz-web/xmlparsing" ) const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" func RandString(length int) string { b := make([]byte, length) for i := range b { b[i] = charset[rand.Intn(len(charset))] } return string(b) } type Note struct { Id string Tokens Tokens } type LenzParseState struct { Tokens Tokens Notes []Note Count []Note LC int PC string CloseElement bool Break bool PageBreak bool LineBreak bool } func (s *LenzParseState) String() string { builder := strings.Builder{} builder.WriteString(outToken{Name: "div", Classes: []string{"count"}, Type: Element}.String()) for _, c := range s.Count { builder.WriteString(c.Tokens.String()) } builder.WriteString(outToken{Name: "div", Classes: []string{"count"}, Type: EndElement}.String()) s.Tokens.Prepend(outToken{Name: "div", Classes: []string{"fulltext"}, Type: Element}) s.Tokens.AppendEndElement() builder.WriteString(s.Tokens.String()) builder.WriteString(outToken{Name: "div", Classes: []string{"notes"}, Type: Element}.String()) for _, note := range s.Notes { builder.WriteString(note.Tokens.String()) } builder.WriteString(outToken{Name: "div", Classes: []string{"notes"}, Type: EndElement}.String()) return builder.String() } func (s *LenzParseState) AppendNote(note Note) { s.Notes = append(s.Notes, note) } func ParseText(lib *xmlmodels.Library, meta *xmlmodels.Meta) string { if lib == nil { return "" } text := lib.Letters.Item(meta.Letter) if text == nil { return "" } return Parse(lib, meta, text.Content) } func TemplateParse(lib *xmlmodels.Library) func(letter *xmlmodels.Meta, s string) string { return func(letter *xmlmodels.Meta, s string) string { return Parse(lib, letter, s) } } func Parse(lib *xmlmodels.Library, letter *xmlmodels.Meta, s string) string { if len(s) == 0 { return "" } ps := LenzParseState{CloseElement: true, PC: "1"} parser := xmlparsing.NewParser(s) for elem, err := range parser.Iterate() { if err != nil { return err.Error() } if elem.Type < 3 { if elem.Type == xmlparsing.EndElement { if elem.Name == "sidenote" { ps.LineBreak = true } if ps.CloseElement { ps.Tokens.AppendEndElement() } else { ps.CloseElement = true } continue } switch elem.Name { case "insertion": ps.Tokens.AppendDefaultElement(elem) ps.Tokens.AppendDivElement("", "insertion-marker") ps.Tokens.AppendEndElement() case "sidenote": id := RandString(8) ps.Tokens.AppendDefaultElement(elem) ps.Break = false ps.Tokens.AppendCustomAttribute("aria-describedby", id) if elem.Attributes["annotation"] != "" || elem.Attributes["page"] != "" || elem.Attributes["pos"] != "" { note := Note{Id: id} note.Tokens.AppendDivElement(id, "note-sidenote-meta") ps.Tokens.AppendDivElement(id, "inline-sidenote-meta") if elem.Attributes["page"] != "" { note.Tokens.AppendDivElement("", "sidenote-page") note.Tokens.AppendText(elem.Attributes["page"]) note.Tokens.AppendEndElement() ps.Tokens.AppendDivElement("", "sidenote-page") ps.Tokens.AppendText(elem.Attributes["page"]) ps.Tokens.AppendEndElement() } if elem.Attributes["annotation"] != "" { note.Tokens.AppendDivElement("", "sidenote-note") note.Tokens.AppendText(elem.Attributes["annotation"]) note.Tokens.AppendEndElement() ps.Tokens.AppendDivElement("", "sidenote-note") ps.Tokens.AppendText(elem.Attributes["annotation"]) ps.Tokens.AppendEndElement() } if elem.Attributes["pos"] != "" { note.Tokens.AppendDivElement("", "sidenote-pos") note.Tokens.AppendText(elem.Attributes["pos"]) note.Tokens.AppendEndElement() ps.Tokens.AppendDivElement("", "sidenote-pos") ps.Tokens.AppendText(elem.Attributes["pos"]) ps.Tokens.AppendEndElement() } note.Tokens.AppendEndElement() // sidenote-meta ps.Tokens.AppendEndElement() ps.AppendNote(note) } case "note": id := RandString(8) ps.Tokens.AppendLink("#"+id, "nanchor-note") ps.Tokens.AppendEndElement() ps.Tokens.AppendDivElement(id, "note", "note-note") case "nr": ext := elem.Attributes["extent"] if ext == "" { ext = "1" } extno, err := strconv.Atoi(ext) if err != nil { extno = 1 } ps.Tokens.AppendDefaultElement(elem) for i := 0; i < extno; i++ { ps.Tokens.AppendText(" ") } case "hand": id := RandString(8) idno, err := strconv.Atoi(elem.Attributes["ref"]) var person *xmlmodels.PersonDef if err == nil { person = lib.Persons.Item(idno) } hand := "N/A" if person != nil { hand = person.Name } note := Note{Id: id} note.Tokens.AppendDivElement(id, "note-hand") note.Tokens.AppendText(hand) note.Tokens.AppendEndElement() ps.AppendNote(note) ps.Tokens.AppendDivElement(id, "inline-hand") ps.Tokens.AppendText(hand) ps.Tokens.AppendEndElement() ps.Tokens.AppendDivElement("", "hand") ps.Tokens.AppendCustomAttribute("aria-describedby", id) case "line": if val := elem.Attributes["type"]; val != "empty" { ps.LC += 1 if ps.Break { ps.Tokens.AppendEmptyElement("br", ps.PC+"-"+strconv.Itoa(ps.LC)) } ps.Tokens.AppendDefaultElement(elem) // This is for indents, must be closed } else { ps.Tokens.AppendEmptyElement("br", "", "empty") ps.CloseElement = false // Here Indents make no sense, so we dont open an element } ps.LineBreak = true case "page": ps.PC = elem.Attributes["index"] ps.PageBreak = true ps.CloseElement = false default: if !ps.Break && elem.Type == xmlparsing.CharData && strings.TrimSpace(elem.Data) != "" { ps.Break = true } if ps.PageBreak && ps.PC != "1" && elem.Type == xmlparsing.CharData && strings.TrimSpace(elem.Data) != "" { ps.PageBreak = false note := Note{Id: ps.PC} quality := "outside" if !ps.LineBreak { quality = "inside" } ps.Tokens.AppendDivElement("", "eanchor-page", "eanchor-page-"+quality) ps.Tokens.AppendCustomAttribute("aria-describedby", ps.PC) ps.Tokens.AppendEndElement() ps.Tokens.AppendDivElement("", "page-counter", "page-"+quality) ps.Tokens.AppendText(ps.PC) ps.Tokens.AppendEndElement() note.Tokens.AppendDivElement(ps.PC, "page", "page-"+quality) note.Tokens.AppendText(ps.PC) note.Tokens.AppendEndElement() ps.Count = append(ps.Count, note) strings.TrimLeft(elem.Data, " \t\n\r") } if ps.LineBreak && elem.Type == xmlparsing.CharData && strings.TrimSpace(elem.Data) != "" { strings.TrimLeft(elem.Data, " \t\n\r") ps.LineBreak = false } ps.Tokens.AppendDefaultElement(elem) } } } return ps.String() }