A tool for archiving & converting scans of postcards, and information about them.
1package types
2
3import (
4 "encoding/json"
5 "encoding/xml"
6 "fmt"
7 "html"
8 "sort"
9 "strings"
10)
11
12type AnnotatedText struct {
13 Text string `json:"text,omitempty" yaml:"text,omitempty"`
14 Annotations []Annotation `json:"annotations,omitempty" yaml:"annotations,omitempty"`
15}
16
17type Annotation struct {
18 Type AnnotationType `json:"type"`
19 Value string `json:"value,omitempty"`
20 // The *byte* count just before this annotation starts
21 Start uint `json:"start"`
22 // The *byte* count just after this annotation ends
23 End uint `json:"end"`
24}
25
26type AnnotationType string
27
28const (
29 ATLocale AnnotationType = "locale"
30 ATEmphasis AnnotationType = "em"
31 ATStrong AnnotationType = "strong"
32 ATUnderline AnnotationType = "underline"
33)
34
35func (at AnnotatedText) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
36 enc, err := json.Marshal(at)
37 if err != nil {
38 return err
39 }
40 return e.EncodeElement(string(enc), start)
41}
42
43type token struct {
44 isOpen bool
45 pos uint
46 Annotation
47}
48
49type byPos []token
50
51func (a byPos) Len() int { return len(a) }
52func (a byPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
53func (a byPos) Less(i, j int) bool {
54 return a[i].pos < a[j].pos
55}
56
57func (at AnnotatedText) HTML() string {
58 var tokens []token
59 for _, a := range at.Annotations {
60 tokens = append(tokens, token{
61 isOpen: true,
62 pos: a.Start,
63 Annotation: a,
64 }, token{
65 isOpen: false,
66 pos: a.End,
67 Annotation: a,
68 })
69 }
70
71 sort.Sort(byPos(tokens))
72
73 pos := uint(0)
74 var outHTML strings.Builder
75 for _, tok := range tokens {
76 if tok.pos > pos {
77 outHTML.WriteString(html.EscapeString(at.Text[pos:tok.pos]))
78 pos = tok.pos
79 }
80 outHTML.WriteString(tok.Annotation.HTMLTag(tok.isOpen))
81 }
82 if pos < uint(len(at.Text)) {
83 outHTML.WriteString(html.EscapeString(at.Text[pos:]))
84 }
85
86 return outHTML.String()
87}
88
89var htmlMap = map[AnnotationType]string{
90 ATEmphasis: "em",
91 ATStrong: "strong",
92 ATUnderline: "u",
93}
94
95func (a Annotation) HTMLTag(isOpen bool) string {
96 switch a.Type {
97 case ATLocale:
98 if isOpen {
99 return fmt.Sprintf(`<span lang="%s">`, a.Value)
100 } else {
101 return "</span>"
102 }
103 default:
104 tag, ok := htmlMap[a.Type]
105 if !ok {
106 return ""
107 }
108 if isOpen {
109 return fmt.Sprintf("<%s>", tag)
110 } else {
111 return fmt.Sprintf("</%s>", tag)
112 }
113 }
114}