bbcode - processor.go
1 package bbcode
2
3 import (
4 "bytes"
5 "io"
6 "strings"
7
8 "vimagination.zapto.org/parser"
9 )
10
11 // Processor contains methods necessary for creating custom Handler's
12 type Processor struct {
13 w io.Writer
14 err error
15 p parser.Parser
16 bbCode *BBCode
17 }
18
19 // Write writes to the underlying writer.
20 // The error is stored and does not need to be handled
21 func (p *Processor) Write(b []byte) (int, error) {
22 if p.err != nil {
23 return 0, p.err
24 }
25 var n int
26 n, p.err = p.w.Write(b)
27 return n, p.err
28 }
29
30 // Process will continue processing the bbCode until it gets to an end tag
31 // which matches the tag name given, or until it reaches the end of the input.
32 // It returns true if the end tag was found, or false otherwise.
33 func (p *Processor) Process(untilTag string) bool {
34 for {
35 switch t := p.Get().(type) {
36 case Text:
37 p.printText(t)
38 case OpenTag:
39 p.ProcessTag(t)
40 case CloseTag:
41 if strings.EqualFold(t.Name, untilTag) {
42 return true
43 }
44 p.printCloseTag(t)
45 default:
46 return false
47 }
48 }
49 }
50
51 // GetContents grabs the raw contents of a tag and returns it as a string
52 func (p *Processor) GetContents(untilTag string) string {
53 if p.err != nil {
54 return ""
55 }
56 w := p.w
57 b := bytes.NewBuffer(make([]byte, 0, 1024))
58 p.w = b
59 t := p.bbCode.text
60 p.bbCode.text = PlainText
61 Loop:
62 for {
63 switch t := p.Get().(type) {
64 case Text:
65 p.printText(t)
66 case OpenTag:
67 p.printOpenTag(t)
68 case CloseTag:
69 if strings.EqualFold(t.Name, untilTag) {
70 break Loop
71 }
72 p.printCloseTag(t)
73 default:
74 break Loop
75 }
76 }
77 p.bbCode.text = t
78 p.w = w
79 return b.String()
80 }
81
82 // ProcessTag will process the given tag as normal
83 func (p *Processor) ProcessTag(t OpenTag) {
84 h := p.getTagHandler(t.Name)
85 if h == nil {
86 p.printOpenTag(t)
87 } else {
88 var attr string
89 if t.Attr != nil {
90 attr = *t.Attr
91 }
92 h.Handle(p, attr)
93 }
94 }
95
96 func (p *Processor) getTagHandler(name string) Handler {
97 for _, tag := range p.bbCode.tags {
98 if strings.EqualFold(tag.Name(), name) {
99 return tag
100 }
101 }
102 return nil
103 }
104
105 // Get returns the next token.
106 // It will be either a Text, OpenTag or a CloseTag
107 func (p *Processor) Get() interface{} {
108 phrase, _ := p.p.GetPhrase()
109 switch phrase.Type {
110 case phraseText:
111 text := make(Text, 0, len(phrase.Data))
112 for _, t := range phrase.Data {
113 text = append(text, t.Data)
114 }
115 return text
116 case phraseOpen:
117 tag := OpenTag{
118 Name: phrase.Data[0].Data,
119 }
120 if len(phrase.Data) > 1 {
121 tag.Attr = &phrase.Data[1].Data
122 }
123 return tag
124 case phraseClose:
125 return CloseTag{Name: phrase.Data[0].Data}
126 }
127 return nil
128 }
129
130 // Print writes the textual representation of the given token to the output,
131 // using the text Handler
132 func (p *Processor) Print(t interface{}) {
133 switch t := t.(type) {
134 case string:
135 p.bbCode.text.Handle(p, t)
136 case Text:
137 p.printText(t)
138 case OpenTag:
139 p.printOpenTag(t)
140 case CloseTag:
141 p.printCloseTag(t)
142 }
143 }
144
145 func (p *Processor) printText(t Text) {
146 for _, str := range t {
147 p.bbCode.text.Handle(p, str)
148 }
149 }
150
151 func (p *Processor) printOpenTag(t OpenTag) {
152 p.bbCode.text.Handle(p, p.bbCode.tks.openTag)
153 p.bbCode.text.Handle(p, t.Name)
154 if t.Attr != nil {
155 p.bbCode.text.Handle(p, p.bbCode.tks.attributeSep)
156 p.bbCode.text.Handle(p, *t.Attr)
157 }
158 p.bbCode.text.Handle(p, p.bbCode.tks.closeTag)
159 }
160
161 func (p *Processor) printCloseTag(t CloseTag) {
162 p.bbCode.text.Handle(p, p.bbCode.tks.openTag)
163 p.bbCode.text.Handle(p, p.bbCode.tks.closingTag)
164 p.bbCode.text.Handle(p, t.Name)
165 p.bbCode.text.Handle(p, p.bbCode.tks.closeTag)
166 }
167
168 // Text is a token containing simple textual data
169 type Text []string
170
171 // OpenTag is a token containing the name of the tag and a possible attribute.
172 type OpenTag struct {
173 Name string
174 Attr *string
175 }
176
177 // CloseTag is a token containing the name of the tag
178 type CloseTag struct {
179 Name string
180 }
181