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