From a12c15417b74bfc0cdf5354fdce09b9abd36c853 Mon Sep 17 00:00:00 2001 From: aaron <462826@qq.com> Date: Tue, 15 Sep 2020 09:52:04 +0800 Subject: [PATCH] add article convert command and doc --- demo/article.yaml | 208 --------------------- {data/words => demo/article}/article.txt | 0 res/en/usage.txt | 36 ++-- res/zh/usage.txt | 2 + src/service/article.go | 226 +++++++++++++++++++++++ src/utils/file/file.go | 38 ++++ src/zd.go | 8 + test/article/common.go | 63 ------- test/article/generate_yaml_test.go | 210 --------------------- tmp/cache/.data.db | Bin 3633152 -> 3665920 bytes 10 files changed, 293 insertions(+), 498 deletions(-) delete mode 100644 demo/article.yaml rename {data/words => demo/article}/article.txt (100%) create mode 100644 src/service/article.go delete mode 100644 test/article/common.go diff --git a/demo/article.yaml b/demo/article.yaml deleted file mode 100644 index e3784d4..0000000 --- a/demo/article.yaml +++ /dev/null @@ -1,208 +0,0 @@ -author: ZenData -desc: Generated from article text automatically -from: words.v1 -title: automation -type: article -version: "" - -fields: - - field: "1" - limit: 1 - postfix: "" - prefix: 秋天的天气 - rand: true - select: xingrongci-tianqi-qiuji-hao - useLastSameValue: false - where: "true" - - field: "3" - limit: 1 - postfix: "" - prefix: 、 - rand: true - select: xingrongci-tianqi-qiuji-hao - useLastSameValue: false - where: "true" - - field: "5" - limit: 1 - postfix: "" - prefix: , - rand: true - select: xingrongci-waimao-nvxing - useLastSameValue: false - where: "true" - - field: "6" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-xing - useLastSameValue: false - where: mingci-xing = 'f' - - field: "6" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-mingzi - useLastSameValue: false - where: mingci-mingzi = 'f' - - field: "7" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: fuci-chengdufuci - useLastSameValue: false - where: "true" - - field: "8" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: xingrongcizuoweiyu-qingxu-kuaile - useLastSameValue: false - where: "true" - - field: "11" - limit: 1 - postfix: "" - prefix: ,因为 - rand: true - select: mingci-chenghu - useLastSameValue: false - where: mingci-chenghu = 'f' - - field: "12" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: dongci-jiwu_mingci-yiyuan - useLastSameValue: false - where: "true" - - field: "14" - limit: 1 - postfix: "" - prefix: 带她去 - rand: true - select: mingci-dedian-mingshan - useLastSameValue: false - where: "true" - - field: "15" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: dongci-jiwu_mingci-dongzuo-yanbu-zhongxing - useLastSameValue: false - where: "true" - - field: "16" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: xingrongci-yanse-zhiwu-shumu - useLastSameValue: false - where: "true" - - field: "17" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-zhiwu-shumu - useLastSameValue: false - where: "true" - - field: "18" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: lianci-binglieguanxi - useLastSameValue: false - where: "true" - - field: "19" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: xingrongci-yanse-zhiwu-huahui - useLastSameValue: false - where: "true" - - field: "20" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-zhiwu-huahui - useLastSameValue: false - where: "true" - - field: "22" - limit: 1 - postfix: "" - prefix: 。 - rand: true - select: xingrongci-waimao-nvxing - useLastSameValue: true - where: "true" - - field: "23" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-xing - useLastSameValue: true - where: mingci-xing = 'f' - - field: "23" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: mingci-mingzi - useLastSameValue: true - where: mingci-mingzi = 'f' - - field: "24" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: fuci-chengdufuci - useLastSameValue: false - where: "true" - - field: "25" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: xingrongcizuoweiyu-qingxu-kuaile - useLastSameValue: false - where: "true" - - field: "27" - limit: 1 - postfix: "" - prefix: , - rand: true - select: fuci-xingrongcizuofuci-qingxu-kuaile - useLastSameValue: false - where: "true" - - field: "28" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: dongci-jiwu_mingci-dongzuo-shoubi-qinqie - useLastSameValue: false - where: "true" - - field: "29" - limit: 1 - postfix: "" - prefix: "" - rand: true - select: xingrongci-xingge-jiji - useLastSameValue: false - where: "true" - - field: "30" - limit: 1 - postfix: "\n\n" - prefix: "" - rand: true - select: mingci-chenghu - useLastSameValue: true - where: mingci-chenghu = 'f' diff --git a/data/words/article.txt b/demo/article/article.txt similarity index 100% rename from data/words/article.txt rename to demo/article/article.txt diff --git a/res/en/usage.txt b/res/en/usage.txt index 0939b44..d7da049 100644 --- a/res/en/usage.txt +++ b/res/en/usage.txt @@ -16,7 +16,7 @@ Parameters: -r --recursive Recursive mode. The default mode is parallel, in which each field loops independently. The value of a field in the recursive mode depends on that of the previous field, which enables the random data. - -p --port Run the HTTP on the specified port. The data in JSON format can be obtained via http://ip/ port. + -p --port Run the HTTP on the specified port. The data in JSON format can be obtained via http:\\ip\ port. Only data generation is supported. -b --bind Listen IP addresses. All IP addresses are listened by default. -R --root The root directory when running HTTP. The client can call the config file under the root directory. @@ -26,6 +26,7 @@ Parameters: You need to specify an output directory by using -o. -D --decode Referring to the specified configuration file, parse the data file specified by -i and output json. Also you can output the readable format via -H. + -a --article Convert article to yaml config file in the dir provided by -o parameter. -l --list List all supported data formats. -v --view View the detailed definition of a data format. @@ -34,24 +35,25 @@ Parameters: Command Line Examples: -$>zd.exe -d demo/default.yaml # Generate 10 lines of data according to the config file specified by -d. -$>zd.exe -c demo/default.yaml # Generate 10 lines of data according to the config file specified by -c. -$>zd.exe -c demo/default.yaml -r # Generate 10 lines of data according to the config file specified by -c recursively. -$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 # Using the parameter of -c and -d at the same time. +$>zd.exe -d demo\default.yaml # Generate 10 lines of data according to the config file specified by -d. +$>zd.exe -c demo\default.yaml # Generate 10 lines of data according to the config file specified by -c. +$>zd.exe -c demo\default.yaml -r # Generate 10 lines of data according to the config file specified by -c recursively. +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 # Using the parameter of -c and -d at the same time. -$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.txt # Output data in original format. -$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.json # Output data in JSON. -$>zd.exe -d demo/default.yaml -c demo/test.yaml -n 100 -o test.xml # Output data in XML. -$>zd.exe -d demo/default.yaml -n 100 -o test.sql -t user # Output the sql inserted into the table user. -$>zd.exe -d demo/default.yaml -o test.sql -t user -s mysql --trim # Remove the prefix and postfix of every field. +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.txt # Output data in original format. +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.json # Output data in JSON. +$>zd.exe -d demo\default.yaml -c demo\test.yaml -n 100 -o test.xml # Output data in XML. +$>zd.exe -d demo\default.yaml -n 100 -o test.sql -t user # Output the sql inserted into the table user. +$>zd.exe -d demo\default.yaml -o test.sql -t user -s mysql --trim # Remove the prefix and postfix of every field. -$>zd.exe -i demo/zentao.sql -o db # Generate YAML files for each table by parsing zentao.sql. -$>zd.exe -c demo/default.yaml -i test.txt --decode # Parse the file specified by -i according to the config of -d. +$>zd.exe -i demo\zentao.sql -o db # Generate YAML files for each table by parsing zentao.sql. +$>zd.exe -c demo\default.yaml -i test.txt --decode # Parse the file specified by -i according to the config of -d. +$>zd.exe -a demo\article.txt -o demo # Convert article to yaml config file in demo dir. $>zd.exe -l # List all build-in data types. -$>zd.exe -v address.cn.v1 # View data types in build-in Excel file data/address/cn.v1.xlsx. +$>zd.exe -v address.cn.v1 # View data types in build-in Excel file data\address\cn.v1.xlsx. $>zd.exe -v address.cn.v1.china # View data items in Excel sheet "china". -$>zd.exe -v ip.v1.yaml # View data in build-in instances defined in yaml/ip/v1.yaml。 +$>zd.exe -v ip.v1.yaml # View data in build-in instances defined in yaml\ip\v1.yaml。 Service Example: @@ -60,6 +62,6 @@ $zd.exe -p 80 -R d:\zd\config # Listen port 80. Use d:\zd\config as th Client Call: -$curl http://localhost:8848/?d=demo/default.yaml&c=demo/config.yaml&n=100 # Specify the server config file via GET. -$curl http://localhost:8848/?default=demo/default.yaml&output=test.sql&table=user # Parameter names can be full. -$curl -i -X POST http://localhost:8848?lines=3 -F default=@demo/default.yaml # The config can be uploaded via POST. +$curl http:\\localhost:8848\?d=demo\default.yaml&c=demo\config.yaml&n=100 # Specify the server config file via GET. +$curl http:\\localhost:8848\?default=demo\default.yaml&output=test.sql&table=user # Parameter names can be full. +$curl -i -X POST http:\\localhost:8848?lines=3 -F default=@demo\default.yaml # The config can be uploaded via POST. diff --git a/res/zh/usage.txt b/res/zh/usage.txt index 52e0a9c..8577705 100644 --- a/res/zh/usage.txt +++ b/res/zh/usage.txt @@ -20,6 +20,7 @@ ZenData是一款通用的数据生成工具,您可以使用yaml文件来定义 -i --input 指定一个schema文件,输出每个表的yaml配置文件。需通过-o参数指定一个输出的目录。 -D --decode 根据指定的配置文件,将通过-i参数指定的数据文件解析成json格式。 + -a --article 将指定的文件或目录下扩展名为.txt的文件,转换成文章yaml配置,输出到-o参数指定的目录下。 -l --list 列出所有支持的数据格式。 -v --view 查看某一个数据格式的详细定义。 @@ -41,6 +42,7 @@ $>zd.exe -d demo\default.yaml -n 100 -o test.sql -t user --trim # 输出针 $>zd.exe -i demo\zentao.sql -o db # 根据sql的定义生成各表的yaml文件,存储到db目录里面。 $>zd.exe -c demo\default.yaml -i test.txt --decode # 将-i指定的文件根据-d参数的配置进行解析。 +$>zd.exe -a demo\article.txt -o demo # 转换文章为yaml配置,输出到demo目录下。 $>zd.exe -l # 列出所有內置数据。 $>zd.exe -v address.cn.v1 # 查看內置Excel文件data/address/cn.v1.xlsx中的数据表。 diff --git a/src/service/article.go b/src/service/article.go new file mode 100644 index 0000000..1ddc4cd --- /dev/null +++ b/src/service/article.go @@ -0,0 +1,226 @@ +package service + +import ( + "fmt" + "github.com/easysoft/zendata/src/model" + constant "github.com/easysoft/zendata/src/utils/const" + fileUtils "github.com/easysoft/zendata/src/utils/file" + stringUtils "github.com/easysoft/zendata/src/utils/string" + _ "github.com/mattn/go-sqlite3" + "gopkg.in/yaml.v3" + "path" + "path/filepath" + "strconv" + "strings" +) + +const ( + strLeft = "“" + strRight = "”" + + expLeft = "(" + expRight = ")" + + table = "words.v1" + //src = "data/words" + //dist = "demo" +) +var ( + compares = []string{"=", "!=", ">", "<"} +) + +func ConvertArticle(src, dist string) { + files := make([]string, 0) + if !fileUtils.IsDir(src) { + pth, _ := filepath.Abs(src) + files = append(files, pth) + + if dist == "" { + dist = path.Dir(pth) + } + } else { + fileUtils.GetFilesInDir(src, ".txt", &files) + if dist == "" { + dist = src + } + } + + for _, filePath := range files { + article := fileUtils.ReadFile(filePath) + content := convertToYaml(article, filePath) + + newPath := fileUtils.AddSepIfNeeded(dist) + fileUtils.ChangeFileExt(path.Base(filePath), ".yaml") + fileUtils.WriteFile(newPath, content) + } +} + +func convertToYaml(article, filePath string) (content string) { + sections := parseSections(article) + + conf := createDef(constant.ConfigTypeArticle, table, filePath) + + prefix := "" + for index, section := range sections { + tye := section["type"] + val := section["val"] + + if tye == "exp" { + fields := createFields(index, prefix, val) + conf.XFields = append(conf.XFields, fields...) + + prefix = "" + } else { + prefix += val + } + } + + bytes, _ := yaml.Marshal(&conf) + content = string(bytes) + + // convert yaml format by using a map + m := make(map[string]interface{}) + yaml.Unmarshal([]byte(content), &m) + bytes, _ = yaml.Marshal(&m) + content = string(bytes) + content = strings.Replace(content, "xfields", "\nfields", -1) + + return +} + +func createDef(typ, table, filePath string) (conf model.DefExport) { + conf.Title = "automation" + conf.Author = "ZenData" + conf.From = table + conf.Type = typ + conf.Desc = "Generated from article " + filePath + + return +} + +func createFields(index int, prefix, exp string) (fields []model.DefFieldExport) { + field := model.DefFieldExport{} + field.Field = strconv.Itoa(index) + field.Prefix = prefix + field.Rand = true + field.Limit = 1 + + // deal with exp like S:名词-姓+名词-名字=F + exp = strings.ToLower(strings.TrimSpace(exp)) + expArr := []rune(exp) + + if string(expArr[0]) == "s" && (string(expArr[1]) == ":" || string(expArr[1]) == ":") { + exp = string(expArr[2:]) + expArr = expArr[2:] + field.UseLastSameValue = true + } + + if strings.Index(exp, "=") == len(exp) - 2 { + exp = string(expArr[:len(expArr) - 2]) + field.Select = stringUtils.GetPinyin(exp) + field.Where = fmt.Sprintf("%s = '%s'", field.Select, string(expArr[len(expArr) - 1])) + } else { + field.Select = stringUtils.GetPinyin(exp) + field.Where = "true" + //field.Where = getPinyin(exp) + " = 'y'" + } + + if strings.Index(field.Select, "+") < 0 { + fields = append(fields, field) + } else if strings.Index(field.Select, "+") > 0 { // include more than one field, split to two + arr := strings.Split(field.Where, "=") + right := "" + if len(arr) > 1 { + right = arr[1] + } + + items := strings.Split(field.Select, "+") + for _, item := range items { + var objClone interface{} = field + fieldClone := objClone.(model.DefFieldExport) + fieldClone.Select = item + + if len(arr) > 1 { // has conditions + fieldClone.Where = item + " = " + right + } + + fields = append(fields, fieldClone) + } + } + + return +} + +func parseSections(content string) (sections []map[string]string) { + strStart := false + expStart := false + + content = strings.TrimSpace(content) + runeArr := []rune(content) + + section := "" + for i := 0; i < len(runeArr); i++ { + item := runeArr[i] + str := string(item) + + isCouple, duplicateStr := isCouple(i, runeArr) + if isCouple { + section += duplicateStr + i += 1 + } else if strStart && str == strRight { // str close + addSection(section, "str", §ions) + + strStart = false + section = "" + } else if expStart && str == expRight { // exp close + addSection(section, "exp", §ions) + + expStart = false + section = "" + } else if !strStart && !expStart && str == strLeft { // str start + if section != "" && strings.TrimSpace(section) != "+" { + addSection(section, "str", §ions) + } + + strStart = true + section = "" + } else if !strStart && !expStart && str == expLeft { // exp start + if section != "" && strings.TrimSpace(section) != "+" { + addSection(section, "str", §ions) + } + + expStart = true + section = "" + } else { + section += str + } + } + + return +} + +func addSection(str, typ string, arr *[]map[string]string) { + mp := map[string]string{} + mp["type"] = typ + mp["val"] = str + + *arr = append(*arr, mp) +} + +func isCouple(i int, arr []rune) (isCouple bool, duplicateStr string) { + if string(arr[i]) == strLeft && (i + 1 < len(arr) && string(arr[i + 1]) == strLeft) { + isCouple = true + duplicateStr = string(arr[i]) + } else if string(arr[i]) == strRight && (i + 1 < len(arr) && string(arr[i + 1]) == strRight) { + isCouple = true + duplicateStr = string(arr[i]) + } else if string(arr[i]) == expLeft && (i + 1 < len(arr) && string(arr[i + 1]) == expLeft) { + isCouple = true + duplicateStr = string(arr[i]) + } else if string(arr[i]) == expRight && (i + 1 < len(arr) && string(arr[i + 1]) == expRight) { + isCouple = true + duplicateStr = string(arr[i]) + } + + return +} + diff --git a/src/utils/file/file.go b/src/utils/file/file.go index a288815..76b2259 100644 --- a/src/utils/file/file.go +++ b/src/utils/file/file.go @@ -302,4 +302,42 @@ func GetFileName(filePath string) string { fileName = strings.TrimSuffix(fileName, path.Ext(filePath)) return fileName +} + +func GetFilesInDir(folder, ext string, files *[]string) { + folder, _ = filepath.Abs(folder) + + if !IsDir(folder) { + if path.Ext(folder) == ext { + *files = append(*files, folder) + } + + return + } + + dir, err := ioutil.ReadDir(folder) + if err != nil { + return + } + + for _, fi := range dir { + name := fi.Name() + if commonUtils.IngoreFile(name) { + continue + } + + filePath := AddSepIfNeeded(folder) + name + if fi.IsDir() { + GetFilesInDir(filePath, ext, files) + } else if strings.Index(name, "~") != 0 && path.Ext(filePath) == ext { + *files = append(*files, filePath) + } + } +} + +func ChangeFileExt(filePath, ext string) string { + ret := strings.TrimSuffix(filePath, path.Ext(filePath)) + ret += ext + + return ret } \ No newline at end of file diff --git a/src/zd.go b/src/zd.go index 59fd451..f52a018 100644 --- a/src/zd.go +++ b/src/zd.go @@ -38,6 +38,8 @@ var ( format = constant.FormatText decode bool + article string + listRes bool viewRes string viewDetail string @@ -96,6 +98,9 @@ func main() { flagSet.BoolVar(&decode, "D", false, "") flagSet.BoolVar(&decode, "decode", false, "") + flagSet.StringVar(&article, "a", "", "") + flagSet.StringVar(&article, "article", "", "") + flagSet.StringVar(&vari.Ip, "b", "", "") flagSet.StringVar(&vari.Ip, "bind", "", "") flagSet.IntVar(&vari.Port, "p", 0, "") @@ -149,6 +154,9 @@ func main() { } else if decode { gen.Decode(defaultFile, configFile, fields, input, output) return + } else if article != "" { + service.ConvertArticle(article, output) + return } if vari.Ip != "" || vari.Port != 0 { diff --git a/test/article/common.go b/test/article/common.go deleted file mode 100644 index fd07a16..0000000 --- a/test/article/common.go +++ /dev/null @@ -1,63 +0,0 @@ -package main - -import ( - "github.com/Chain-Zhang/pinyin" - commonUtils "github.com/easysoft/zendata/src/utils/common" - fileUtils "github.com/easysoft/zendata/src/utils/file" - _ "github.com/mattn/go-sqlite3" - "io/ioutil" - "path" - "path/filepath" - "strings" -) - -func getFilesInDir(folder, ext string, files *[]string) { - folder, _ = filepath.Abs(folder) - - if !fileUtils.IsDir(folder) { - if path.Ext(folder) == ext { - *files = append(*files, folder) - } - - return - } - - dir, err := ioutil.ReadDir(folder) - if err != nil { - return - } - - for _, fi := range dir { - name := fi.Name() - if commonUtils.IngoreFile(name) { - continue - } - - filePath := fileUtils.AddSepIfNeeded(folder) + name - if fi.IsDir() { - getFilesInDir(filePath, ext, files) - } else if strings.Index(name, "~") != 0 && path.Ext(filePath) == ext { - *files = append(*files, filePath) - } - } -} - -func getFileName(filePath string) string { - fileName := path.Base(filePath) - fileName = strings.TrimSuffix(fileName, path.Ext(filePath)) - - return fileName -} - -func changeFileExt(filePath, ext string) string { - ret := strings.TrimSuffix(filePath, path.Ext(filePath)) - ret += ext - - return ret -} - -func getPinyin(word string) string { - p, _ := pinyin.New(word).Split("").Mode(pinyin.WithoutTone).Convert() - - return p -} \ No newline at end of file diff --git a/test/article/generate_yaml_test.go b/test/article/generate_yaml_test.go index 7ee5244..06ab7d0 100644 --- a/test/article/generate_yaml_test.go +++ b/test/article/generate_yaml_test.go @@ -1,211 +1 @@ package main - -import ( - "fmt" - "github.com/easysoft/zendata/src/model" - constant "github.com/easysoft/zendata/src/utils/const" - fileUtils "github.com/easysoft/zendata/src/utils/file" - _ "github.com/mattn/go-sqlite3" - "gopkg.in/yaml.v3" - "path" - "strconv" - "strings" - "testing" -) - -const ( - strLeft = "“" - strRight = "”" - - expLeft = "(" - expRight = ")" - - src = "data/words" - dist = "demo" -) -var ( - compares = []string{"=", "!=", ">", "<"} -) - -func TestGenerate(ts *testing.T) { - files := make([]string, 0) - getFilesInDir(src, ".txt", &files) - - for _, filePath := range files { - article := fileUtils.ReadFile(filePath) - content := convertToYaml(article) - - newPath := fileUtils.AddSepIfNeeded(dist) + changeFileExt(path.Base(filePath), ".yaml") - fileUtils.WriteFile(newPath, content) - } -} - -func convertToYaml(article string) (content string) { - sections := parseSections(article) - - conf := createDef(constant.ConfigTypeArticle, "words.v1") - - prefix := "" - for index, section := range sections { - tye := section["type"] - val := section["val"] - - if tye == "exp" { - fields := createFields(index, prefix, val) - conf.XFields = append(conf.XFields, fields...) - - prefix = "" - } else { - prefix += val - } - } - - bytes, _ := yaml.Marshal(&conf) - content = string(bytes) - - // convert yaml format by using a map - m := make(map[string]interface{}) - yaml.Unmarshal([]byte(content), &m) - bytes, _ = yaml.Marshal(&m) - content = string(bytes) - content = strings.Replace(content, "xfields", "\nfields", -1) - - return -} - -func createDef(typ, table string) (conf model.DefExport) { - conf.Title = "automation" - conf.Author = "ZenData" - conf.From = table - conf.Type = typ - conf.Desc = "Generated from article text automatically" - - return -} - -func createFields(index int, prefix, exp string) (fields []model.DefFieldExport) { - field := model.DefFieldExport{} - field.Field = strconv.Itoa(index) - field.Prefix = prefix - field.Rand = true - field.Limit = 1 - - // deal with exp like S:名词-姓+名词-名字=F - exp = strings.ToLower(strings.TrimSpace(exp)) - expArr := []rune(exp) - - if string(expArr[0]) == "s" && (string(expArr[1]) == ":" || string(expArr[1]) == ":") { - exp = string(expArr[2:]) - expArr = expArr[2:] - field.UseLastSameValue = true - } - - if strings.Index(exp, "=") == len(exp) - 2 { - exp = string(expArr[:len(expArr) - 2]) - field.Select = getPinyin(exp) - field.Where = fmt.Sprintf("%s = '%s'", field.Select, string(expArr[len(expArr) - 1])) - } else { - field.Select = getPinyin(exp) - field.Where = "true" - //field.Where = getPinyin(exp) + " = 'y'" - } - - if strings.Index(field.Select, "+") < 0 { - fields = append(fields, field) - } else if strings.Index(field.Select, "+") > 0 { // include more than one field, split to two - arr := strings.Split(field.Where, "=") - right := "" - if len(arr) > 1 { - right = arr[1] - } - - items := strings.Split(field.Select, "+") - for _, item := range items { - var objClone interface{} = field - fieldClone := objClone.(model.DefFieldExport) - fieldClone.Select = item - - if len(arr) > 1 { // has conditions - fieldClone.Where = item + " = " + right - } - - fields = append(fields, fieldClone) - } - } - - return -} - -func parseSections(content string) (sections []map[string]string) { - strStart := false - expStart := false - - content = strings.TrimSpace(content) - runeArr := []rune(content) - - section := "" - for i := 0; i < len(runeArr); i++ { - item := runeArr[i] - str := string(item) - - isCouple, duplicateStr := isCouple(i, runeArr) - if isCouple { - section += duplicateStr - i += 1 - } else if strStart && str == strRight { // str close - addSection(section, "str", §ions) - - strStart = false - section = "" - } else if expStart && str == expRight { // exp close - addSection(section, "exp", §ions) - - expStart = false - section = "" - } else if !strStart && !expStart && str == strLeft { // str start - if section != "" && strings.TrimSpace(section) != "+" { - addSection(section, "str", §ions) - } - - strStart = true - section = "" - } else if !strStart && !expStart && str == expLeft { // exp start - if section != "" && strings.TrimSpace(section) != "+" { - addSection(section, "str", §ions) - } - - expStart = true - section = "" - } else { - section += str - } - } - - return -} - -func addSection(str, typ string, arr *[]map[string]string) { - mp := map[string]string{} - mp["type"] = typ - mp["val"] = str - - *arr = append(*arr, mp) -} - -func isCouple(i int, arr []rune) (isCouple bool, duplicateStr string) { - if string(arr[i]) == strLeft && (i + 1 < len(arr) && string(arr[i + 1]) == strLeft) { - isCouple = true - duplicateStr = string(arr[i]) - } else if string(arr[i]) == strRight && (i + 1 < len(arr) && string(arr[i + 1]) == strRight) { - isCouple = true - duplicateStr = string(arr[i]) - } else if string(arr[i]) == expLeft && (i + 1 < len(arr) && string(arr[i + 1]) == expLeft) { - isCouple = true - duplicateStr = string(arr[i]) - } else if string(arr[i]) == expRight && (i + 1 < len(arr) && string(arr[i + 1]) == expRight) { - isCouple = true - duplicateStr = string(arr[i]) - } - - return -} \ No newline at end of file diff --git a/tmp/cache/.data.db b/tmp/cache/.data.db index ade5af9cfef448ece1d7882b30e8e71c57805e28..13c6216d845f21705c64ba03e08f7ce4b9a28a88 100644 GIT binary patch delta 26972 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 delta 6003 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 UqMmOdabRwIvo;ia(lz;i0ie9mZU6uP -- GitLab