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  }