1 package bbcode 2 3 import ( 4 "bytes" 5 "strings" 6 "text/template" 7 ) 8 9 // Handler is an interface that represents the text and tag processors. 10 type Handler interface { 11 // Name returns the name of the bbCode tag that this will be used for. 12 // Returning an empty string indicates that this Handler should be used 13 // for text handling. 14 Name() string 15 16 // Handle takes a pointer to the Processor and the attribute to the tag. 17 Handle(*Processor, string) 18 } 19 20 // Tag is a simple Handler that just outputs open and closing tags. 21 type Tag struct { 22 name string 23 open, close []byte 24 } 25 26 // NewTag creates a simple Handler that outputs an open and close tag. 27 // For example, the following would be used to create a tag for handling bold: 28 // 29 // NewTag("b", []byte("<b>"), []("</b>")) 30 func NewTag(name string, open, close []byte) *Tag { 31 return &Tag{ 32 name: name, 33 open: open, 34 close: close, 35 } 36 } 37 38 // Name returns the name of the tag. 39 func (t *Tag) Name() string { 40 return t.name 41 } 42 43 // Open outputs the opening of the tag. 44 func (t *Tag) Open(p *Processor, attr string) { 45 p.Write(t.open) 46 } 47 48 // Close outputs the closing of the tag. 49 func (t *Tag) Close(p *Processor, attr string) { 50 p.Write(t.close) 51 } 52 53 // Handle processes the tag. 54 func (t *Tag) Handle(p *Processor, attr string) { 55 t.Open(p, attr) 56 p.Process(t.name) 57 t.Close(p, attr) 58 } 59 60 // AttrFilterer is used with AttributeTag to provide an attribute filter for 61 // the AttributeTag. It is used to process the parsed attribute for writing. 62 type AttrFilterer interface { 63 AttrFilter(string) []byte 64 } 65 66 var defaultAttrFilter attrFilter 67 68 type attrFilter struct{} 69 70 func (attrFilter) AttrFilter(attr string) []byte { 71 if attr != "" { 72 if !strings.ContainsAny(attr, "'\"&<>\000") { 73 return []byte(attr) 74 } 75 76 var b bytes.Buffer 77 78 template.HTMLEscape(&b, []byte(attr)) 79 80 return b.Bytes() 81 } 82 83 return nil 84 } 85 86 // AttrFilterFunc is a wrapper for a func so that it satisfies the AttrFilterer 87 // interface. 88 type AttrFilterFunc func(string) []byte 89 90 // AttrFilter satisfies the AttrFilterer interface. 91 func (a AttrFilterFunc) AttrFilter(attr string) []byte { 92 return a(attr) 93 } 94 95 // AttributeTag is a simple Handler that outputs and open tag, with an 96 // attribute, and a close tag. 97 type AttributeTag struct { 98 name string 99 open, openClose, attrOpen, attrClose, close []byte 100 filter AttrFilterer 101 } 102 103 // NewAttributeTag creates a new Attribute Tag. 104 // The open byte slice is used to start the open tag and the openClose is used 105 // to close the open tag. 106 // The attrOpen and attrClose byte slices are used to surround the filtered 107 // attribute, within the open tag. 108 // Lastly, the close byte slice is used for the closing tag. 109 // The filter is used to modify the attribute, whether to correct formatting 110 // errors, or to validate. If a nil slice is returned, then no attribute is 111 // outputted. 112 // For example the following would create a colour tag for handling font colour: 113 // 114 // NewAttributeTag("colour", 115 // []byte("<span"), 116 // []byte(">"), 117 // []byte(" style=\"color: "), 118 // []byte("\""), 119 // []byte("</span>"), 120 // colourChecker) 121 // 122 // A nil filter means that the attr will be written to the output with HTML 123 // encoding. 124 func NewAttributeTag(name string, open, openClose, attrOpen, attrClose, close []byte, filter AttrFilterer) *AttributeTag { 125 if filter == nil { 126 filter = &defaultAttrFilter 127 } 128 129 return &AttributeTag{ 130 name: name, 131 open: open, 132 openClose: openClose, 133 attrOpen: attrOpen, 134 attrClose: attrClose, 135 close: close, 136 filter: filter, 137 } 138 } 139 140 // Name returns the name of the tag. 141 func (a *AttributeTag) Name() string { 142 return a.name 143 } 144 145 // Open outputs the opening of the tag. 146 func (a *AttributeTag) Open(p *Processor, attr string) { 147 p.Write(a.open) 148 149 if filtered := a.filter.AttrFilter(attr); filtered != nil { 150 p.Write(a.attrOpen) 151 p.Write(filtered) 152 p.Write(a.attrClose) 153 } 154 155 p.Write(a.openClose) 156 } 157 158 // Close outputs the closing of the tag. 159 func (a *AttributeTag) Close(p *Processor, attr string) { 160 p.Write(a.close) 161 } 162 163 // Handle processes the tag. 164 func (a *AttributeTag) Handle(p *Processor, attr string) { 165 a.Open(p, attr) 166 p.Process(a.name) 167 a.Close(p, attr) 168 } 169 170 // OpenClose is an interface for the methods required by FilterTag. Both Tag 171 // and AttributeTag implement this interface. 172 type OpenClose interface { 173 Name() string 174 Open(*Processor, string) 175 Close(*Processor, string) 176 } 177 178 // FilterTag is a Handler that filters which child nodes are processed. 179 type FilterTag struct { 180 OpenClose 181 filter func(string) bool 182 } 183 184 // NewFilterTag creates a Handler that filters which child nodes are processed. 185 // The filter takes the name of the tag and returns a bool determining whether 186 // the tag will be processed as a tag or as text. 187 // An empty tag name in the filter is used to determine is text is processed. 188 func NewFilterTag(o OpenClose, filter func(string) bool) *FilterTag { 189 return &FilterTag{ 190 OpenClose: o, 191 filter: filter, 192 } 193 } 194 195 // Handle processes the tag, using its filter to determine which children are 196 // also processed. 197 func (f *FilterTag) Handle(p *Processor, attr string) { 198 f.Open(p, attr) 199 200 allowText := f.filter("") 201 name := f.Name() 202 203 Loop: 204 for { 205 switch t := p.Get().(type) { 206 case Text: 207 if allowText { 208 p.Print(t) 209 } 210 case OpenTag: 211 if f.filter(t.Name) { 212 p.ProcessTag(t) 213 } else if allowText { 214 p.Print(t) 215 } 216 case CloseTag: 217 if strings.EqualFold(t.Name, name) { 218 break Loop 219 } 220 if allowText { 221 p.Print(p) 222 } 223 default: 224 break Loop 225 } 226 } 227 228 f.Close(p, attr) 229 } 230