github.com/zlyuancn/zstr@v0.0.0-20230412074414-14d6b645962f/template.go (about) 1 /* 2 ------------------------------------------------- 3 Author : Zhang Fan 4 date: 2020/7/15 5 Description : 6 ------------------------------------------------- 7 */ 8 9 package zstr 10 11 import ( 12 "bytes" 13 "strconv" 14 ) 15 16 type simpleTemplate struct { 17 data map[string]interface{} 18 keyCounter *counter // key计数器 19 sub int // 下标计数器 20 } 21 22 func newSimpleTemplate(values ...interface{}) *simpleTemplate { 23 return &simpleTemplate{ 24 data: makeMapOfValues(values), 25 keyCounter: newCounter(-1), 26 } 27 } 28 29 var templateVariableNameMap = func() map[int32]struct{} { 30 mm := map[int32]struct{}{ 31 '_': {}, '.': {}, 32 } 33 for i := '0'; i < '9'+1; i++ { 34 mm[i] = struct{}{} 35 } 36 for i := 'a'; i < 'z'+1; i++ { 37 mm[i] = struct{}{} 38 } 39 for i := 'A'; i < 'Z'+1; i++ { 40 mm[i] = struct{}{} 41 } 42 return mm 43 }() 44 45 func (m *simpleTemplate) calculateTemplate(ss []rune, start int) (int, int, string, bool, bool) { 46 var crust, has bool 47 // 查找开头 48 for i := start; i < len(ss); i++ { 49 if ss[i] == '{' { 50 start, crust, has = i, true, true 51 break 52 } 53 if ss[i] == '@' { 54 start, crust, has = i, false, true 55 break 56 } 57 } 58 if !has { 59 return 0, 0, "", false, false 60 } 61 62 // 预检 63 if crust && (len(ss)-start < 4) || (len(ss)-start < 2) { 64 return 0, 0, "", false, false 65 } 66 67 var ok bool 68 if !crust { 69 for i := start + 1; i < len(ss); i++ { 70 _, ok = templateVariableNameMap[ss[i]] 71 if !ok { // 表示查找变量结束了 72 if i-start < 2 || ss[start+1] == '.' || ss[i-1] == '.' { // 操作符占一个位置, 变量长度不可能为0 73 return m.calculateTemplate(ss, i) 74 } 75 return start, i, string(ss[start+1 : i]), false, true // 中间的数据就是需要的数据 76 } 77 } 78 // 可能整个字符串都是需要的数据 79 return start, len(ss), string(ss[start+1:]), false, len(ss)-start >= 2 && ss[start+1] != '.' && ss[len(ss)-1] != '.' 80 } 81 82 // 以下包含{ 83 var variableStart, variableEnd, end int 84 for i := start + 1; i < len(ss); i++ { 85 if variableStart == 0 { 86 if ss[i] == '@' { 87 variableStart = i + 1 88 continue 89 } 90 if ss[i] == ' ' { 91 continue 92 } 93 // {}中间出现非预期的字符, 从这里开始重新扫描 94 return m.calculateTemplate(ss, i) 95 } 96 97 if variableEnd == 0 { 98 if ss[i] == '}' { 99 variableEnd = i 100 end = i + 1 101 break 102 } 103 if ss[i] == ' ' { 104 variableEnd = i 105 continue 106 } 107 _, ok = templateVariableNameMap[ss[i]] 108 if ok { 109 continue 110 } 111 // {}中间出现非预期的字符, 从这里开始重新扫描 112 return m.calculateTemplate(ss, i) 113 } 114 115 if ss[i] == '}' { 116 end = i + 1 117 break 118 } 119 if ss[i] == ' ' { 120 continue 121 } 122 // {}中间出现非预期的字符, 从这里开始重新扫描 123 return m.calculateTemplate(ss, i) 124 } 125 if end == 0 || variableStart >= variableEnd || ss[variableStart] == '.' || ss[variableEnd-1] == '.' { 126 return 0, 0, "", false, false 127 } 128 129 return start, end, string(ss[variableStart:variableEnd]), true, true 130 } 131 132 func (m *simpleTemplate) replaceAllFunc(s string, fn func(full string, variable string, crust bool) string) string { 133 ss := []rune(s) 134 var buff bytes.Buffer 135 for offset := 0; offset < len(ss); { 136 start, end, variable, crust, has := m.calculateTemplate(ss, offset) 137 if !has { 138 buff.WriteString(string(ss[offset:])) 139 break 140 } 141 142 buff.WriteString(string(ss[offset:start])) 143 if crust { 144 buff.WriteString(fn(string(ss[start:end]), variable, crust)) 145 } else { 146 buff.WriteString(fn(string(ss[start:end]), variable, crust)) 147 } 148 offset = end 149 } 150 return buff.String() 151 } 152 153 func (m *simpleTemplate) Render(format string) string { 154 // 替换 {@field} 和 @field, 如果没有设置则不替换 155 result := m.replaceAllFunc(format, func(full string, variable string, crust bool) string { 156 v, ok := m.data[variable+"["+strconv.Itoa(m.keyCounter.Incr(variable))+"]"] 157 if !ok { 158 v, ok = m.data[variable] 159 } 160 if !ok { 161 v, ok = m.data["*["+strconv.Itoa(m.sub)+"]"] 162 } 163 m.sub++ // 每次一定+1 164 if ok { 165 return anyToString(v, true) 166 } 167 if crust { 168 return "" 169 } 170 return full 171 }) 172 return result 173 } 174 175 // 模板渲染, 和Render一样, 只是加长了函数名 176 func TemplateRender(format string, values ...interface{}) string { 177 return newSimpleTemplate(values...).Render(format) 178 } 179 180 /* 181 模板渲染 182 183 示例: 184 185 zstr.Render("{@a} { @b } @c text", "va", "vb", "vc") // 按顺序赋值 186 zstr.Render("@a text", map[string]string{"a": "va"}) // 指定变量名赋值 187 zstr.Render("@a @b @c", zstr.KV{"a", "aValue"}, zstr.KV{"b", "bValue"}, zstr.KV{"c", "cValue"}) // 指定变量名赋值 188 zstr.Render("@a @b @c", zstr.KVs{{"a", "aValue"}, {"b", "bValue"}, {"c", "cValue"}}) // 指定变量名赋值 189 zstr.Render("@a @a @a", zstr.KV{"a[0]", "1"}, zstr.KV{"a", "2"}) // 指定下标, 指定变量名+下标的优先级比指定变量名更高 190 191 type AA struct { 192 A int 193 B int `render:"b"` 194 } 195 s := zstr.Render(`@A @b`, AA{1, 2}) 196 197 寻值优先级 198 1. 变量名+下标 199 2. 变量名 200 3. 星号+下标 201 如: `a[0]` > `a` > `*[0]` 202 203 注意: 204 205 如果未对模板中的变量进行赋值并且该变量被花括号`{}`包裹, 那么该会被替换为空字符串. 206 */ 207 func Render(format string, values ...interface{}) string { 208 return newSimpleTemplate(values...).Render(format) 209 }