github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgView/kmgGoTpl/kmgGoTpl.go (about) 1 package kmgGoTpl 2 3 import ( 4 "bytes" 5 "fmt" 6 "github.com/bronze1man/kmg/kmgCache" 7 "github.com/bronze1man/kmg/kmgConfig" 8 "github.com/bronze1man/kmg/kmgErr" 9 "github.com/bronze1man/kmg/kmgFile" 10 "go/format" 11 "os" 12 "path/filepath" 13 "strings" 14 ) 15 16 type scope int 17 18 const ( 19 currentScopeStatement scope = 1 20 currentScopeExpression scope = 2 21 currentScopeTpl scope = 3 22 ) 23 24 func BuildTplOneFile(in []byte, isHtml bool) (out []byte, err error) { 25 var transformer transformer 26 transformer.isHtml = isHtml 27 err = kmgErr.PanicToError(func() { 28 out = transformer.mustTransform(in) 29 }) 30 if err != nil { 31 return nil, fmt.Errorf(":%d %s", transformer.lineNum, err) 32 } 33 return out, err 34 } 35 36 func MustBuildTplInDir(path string) { 37 pathList := kmgFile.MustGetAllFiles(path) 38 MustBuildTplInPathList(pathList) 39 } 40 41 func MustBuildTplInPathList(pathList []string) { 42 for _, val := range pathList { 43 ext := filepath.Ext(val) 44 if ext == ".gotpl" { 45 out, err := BuildTplOneFile(kmgFile.MustReadFile(val), false) 46 if err != nil { 47 panic(fmt.Sprintf("%s %s", val, err.Error())) 48 } 49 outFilePath := kmgFile.PathTrimExt(val) + ".go" 50 kmgFile.MustWriteFile(outFilePath, out) 51 } else if ext == ".gotplhtml" { 52 out, err := BuildTplOneFile(kmgFile.MustReadFile(val), true) 53 if err != nil { 54 panic(fmt.Sprintf("%s %s", val, err.Error())) 55 } 56 outFilePath := kmgFile.PathTrimExt(val) + ".go" 57 kmgFile.MustWriteFile(outFilePath, out) 58 } 59 } 60 } 61 62 // 此处路径表示 项目里面的一个路径 63 func MustBuildTplInDirWithCache(root string) { 64 root = kmgConfig.DefaultEnv().PathInProject(root) 65 cachedFileList := cacheFileFilter(root) 66 kmgCache.MustMd5FileChangeCache("kmgGoTpl_"+root, cachedFileList, func() { 67 MustBuildTplInPathList(cachedFileList) 68 }) 69 } 70 71 // 此处返回所有需要进行缓存的文件 72 func cacheFileFilter(root string) []string { 73 output := make([]string, 0, 2048) 74 err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 75 if err != nil { 76 return err 77 } 78 if info.IsDir() { 79 return nil 80 } 81 ext := filepath.Ext(path) 82 if ext == ".gotpl" || ext == ".gotplhtml" { 83 output = append(output, path) 84 output = append(output, kmgFile.PathTrimExt(path)+".go") 85 } 86 return nil 87 }) 88 if err != nil { 89 panic(err) 90 } 91 return output 92 } 93 94 type transformer struct { 95 isHtml bool 96 97 in []byte 98 pos int 99 lineNum int 100 currentScope scope 101 lastScopeBuf bytes.Buffer 102 outBuf bytes.Buffer 103 bracesLevel int //大括号的层数 104 105 //函数是否横跨?>分析 106 isLastFuncTokenWithoutMatchBraces bool 107 isFuncOpenInScope bool //在当前这个scope里面是否有一个函数在里面打开 108 hasFuncOpenBetweenScope bool //是否在scope之间有一个函数正在打开? 109 lastFuncBraceLevel int 110 111 hasBytesPackage bool 112 hasKmgXssPackage bool 113 114 // xss 目前简单采用排除法解决这个问题,很少使用的不考虑,并且信任写模板的人. 不是script,不是urlv 就是kmgXss.H 115 isInScript bool 116 isLastScriptToken bool 117 urlvStatus urlvStatus 118 } 119 120 type urlvStatus int 121 122 const ( 123 urlvStatusNot urlvStatus = 0 124 urlvStatusQuestion urlvStatus = 1 125 urlvStatusKey urlvStatus = 2 126 urlvStatusEqual urlvStatus = 3 127 urlvStatusValue urlvStatus = 4 128 urlvStatusAndSign urlvStatus = 5 129 ) 130 131 func (t *transformer) mustTransform(in []byte) []byte { 132 t.in = in 133 t.currentScope = currentScopeTpl 134 t.lastFuncBraceLevel = -1 135 t.lineNum = 1 136 for t.pos = 0; t.pos < len(t.in); t.pos++ { 137 if t.isMatchString("<?=") { 138 if t.currentScope != currentScopeTpl { 139 panic("<? and ?> not match") 140 } 141 t.endTplScope() 142 t.currentScope = currentScopeExpression 143 t.pos += 2 144 continue 145 } else if t.isMatchString("<?") { 146 if t.currentScope != currentScopeTpl { 147 panic("<? and ?> not match") 148 } 149 t.endTplScope() 150 t.currentScope = currentScopeStatement 151 t.pos += 1 152 continue 153 } else if t.isMatchString("?>") { 154 if t.currentScope == currentScopeTpl { 155 panic("<? and ?> not match") 156 } 157 t.endNotTplScope() 158 t.hasBytesPackage = true 159 t.currentScope = currentScopeTpl 160 t.pos += 1 161 continue 162 } 163 if t.currentScope == currentScopeStatement { 164 if t.isMatchString("func ") { 165 if t.isLastFuncTokenWithoutMatchBraces { 166 panic("func and { not match") 167 } 168 t.isLastFuncTokenWithoutMatchBraces = true 169 } else if in[t.pos] == '{' { 170 t.bracesLevel += 1 171 if t.isLastFuncTokenWithoutMatchBraces { 172 //函数的打开符 173 t.isLastFuncTokenWithoutMatchBraces = false 174 t.lastFuncBraceLevel = t.bracesLevel 175 t.isFuncOpenInScope = true 176 } 177 } else if in[t.pos] == '}' { 178 t.bracesLevel -= 1 179 if t.bracesLevel < 0 { 180 panic("{ and } not match") 181 } 182 if t.lastFuncBraceLevel == t.bracesLevel+1 { 183 t.lastFuncBraceLevel = -1 184 t.isFuncOpenInScope = false 185 //函数的关闭符 186 if t.hasFuncOpenBetweenScope { 187 t.lastScopeBuf.WriteString("return _buf.String()\n") 188 t.hasFuncOpenBetweenScope = false 189 } 190 } 191 } 192 } else if t.currentScope == currentScopeTpl { 193 if t.isHtml { 194 // script 195 if t.isMatchString("<script") { //暂时只支持小写 196 if t.isLastScriptToken { 197 panic("<script and > not match") 198 } 199 t.isLastScriptToken = true 200 } else if t.isMatchString("</script>") { 201 t.isInScript = false 202 } else if in[t.pos] == '>' { 203 if t.isLastScriptToken { 204 t.isLastScriptToken = false 205 t.isInScript = true 206 } 207 } 208 // urlv 209 if in[t.pos] == '?' { 210 // 忽略开头的状态错误 211 t.urlvStatus = urlvStatusQuestion 212 } else if t.urlvStatus == urlvStatusQuestion { //此处恰好是对的,后面不会匹配到? 213 if isUrlvChar(in[t.pos]) { 214 t.urlvStatus = urlvStatusKey 215 } else { 216 t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配 217 } 218 } else if t.urlvStatus == urlvStatusKey { 219 if isUrlvChar(in[t.pos]) { 220 t.urlvStatus = urlvStatusKey 221 } else if in[t.pos] == '=' { 222 t.urlvStatus = urlvStatusEqual 223 } else { 224 t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配 225 } 226 } else if t.urlvStatus == urlvStatusEqual { 227 if isUrlvChar(in[t.pos]) { 228 t.urlvStatus = urlvStatusValue 229 } else { 230 t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配 231 } 232 } else if t.urlvStatus == urlvStatusValue { 233 if isUrlvChar(in[t.pos]) { 234 t.urlvStatus = urlvStatusValue 235 } else if in[t.pos] == '&' { 236 t.urlvStatus = urlvStatusAndSign 237 } else { 238 t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配 239 } 240 } else if t.urlvStatus == urlvStatusAndSign { 241 if isUrlvChar(in[t.pos]) { 242 t.urlvStatus = urlvStatusKey 243 } else { 244 t.urlvStatus = urlvStatusNot //状态计算错误,忽略本次匹配 245 } 246 } 247 } 248 } 249 if in[t.pos] == '\n' { 250 t.lineNum++ 251 } 252 t.lastScopeBuf.WriteByte(t.in[t.pos]) 253 } 254 if t.currentScope == currentScopeTpl { 255 s := strings.TrimSpace(t.lastScopeBuf.String()) 256 if s != "" { 257 panic("find tpl data after <? } ?>") 258 } 259 } 260 output := t.outBuf.Bytes() 261 addPkgList := []string{} 262 if t.hasBytesPackage { 263 addPkgList = append(addPkgList, "bytes") 264 } 265 if t.hasKmgXssPackage { 266 addPkgList = append(addPkgList, "github.com/bronze1man/kmg/kmgXss") 267 } 268 output = addImport(output, addPkgList) 269 f, err := format.Source(output) 270 if err != nil { 271 return output 272 } 273 return f 274 } 275 276 func (t *transformer) endTplScope() { 277 s := t.lastScopeBuf.String() 278 if t.isHtml { 279 s = strings.Trim(s, "\n") 280 } 281 if len(s) > 0 { 282 t.outBuf.WriteString("_buf.WriteString(") 283 if !strings.Contains(s, "`") { 284 t.outBuf.WriteString("`") 285 t.outBuf.WriteString(s) 286 t.outBuf.WriteString("`") 287 } else { 288 t.outBuf.WriteString(fmt.Sprintf("%#v", t.lastScopeBuf.String())) 289 } 290 t.outBuf.WriteString(")\n") 291 } 292 t.lastScopeBuf.Reset() 293 } 294 295 func (t *transformer) endNotTplScope() { 296 if t.currentScope == currentScopeStatement { 297 t.outBuf.WriteString(strings.TrimSpace(t.lastScopeBuf.String())) 298 t.outBuf.WriteString("\n") 299 if t.isFuncOpenInScope { 300 t.isFuncOpenInScope = false 301 t.hasFuncOpenBetweenScope = true 302 t.outBuf.WriteString("var _buf bytes.Buffer\n") 303 } 304 } else if t.currentScope == currentScopeExpression { 305 t.outBuf.WriteString("_buf.WriteString(") 306 s := strings.TrimSpace(t.lastScopeBuf.String()) 307 if t.isHtml { 308 // raw 309 if strings.HasPrefix(s, "raw(") && strings.HasSuffix(s, ")") { 310 t.outBuf.WriteString(s[4 : len(s)-1]) 311 } else if t.urlvStatus == urlvStatusValue || t.urlvStatus == urlvStatusEqual { 312 t.hasKmgXssPackage = true 313 t.outBuf.WriteString("kmgXss.Urlv(") 314 t.outBuf.WriteString(s) 315 t.outBuf.WriteString(")") 316 t.urlvStatus = urlvStatusValue 317 } else if t.isInScript { 318 t.hasKmgXssPackage = true 319 t.outBuf.WriteString("kmgXss.Jsonv(") 320 t.outBuf.WriteString(s) 321 t.outBuf.WriteString(")") 322 } else { 323 t.hasKmgXssPackage = true 324 t.outBuf.WriteString("kmgXss.H(") 325 t.outBuf.WriteString(s) 326 t.outBuf.WriteString(")") 327 } 328 } else { 329 t.outBuf.WriteString(s) 330 } 331 t.outBuf.WriteString(")\n") 332 } 333 t.lastScopeBuf.Reset() 334 } 335 336 func (t *transformer) isMatchString(token string) bool { 337 return bytes.HasPrefix(t.in[t.pos:], []byte(token)) 338 } 339 340 func isAlphanum(b byte) bool { 341 return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') 342 } 343 344 func isUrlvChar(b byte) bool { 345 return (b >= '0' && b <= '9') || (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || 346 b == '.' || b == '-' || b == '_' || b == '%' 347 }