项目地址:json2xmlgit
antlr(ANother Tool for Language Recognition)是一个强大的语法分析器生成工具,它可用于读取,处理,执行和翻译结构化的文本和二进制文件。目前,该工具普遍应用于学术和工业生产领域,同时也是众多语言,工具和框架的基础。
今天咱们就用这个工具实现一个go语言版的json2xml转换器;github
关于一门语言的语法描述叫作grammar, 该工具可以为该语言生成语法解析器,并自动创建语法分析数AST,同时antlr也能自动生成数的遍历器,极大地下降手工coding 语法解析器的成本;json
言归正传,拿json2xml为例,实现一个工具;bash
以macOS为例框架
brew install antlr
// Derived from http://json.org grammar JSON; json: object | array ; object : '{' pair (',' pair)* '}' # AnObject | '{' '}' # NullObject ; array : '[' value (',' value)* ']' # ArrayOfValues | '[' ']' # NullArray ; pair: STRING ':' value ; value : STRING # String | NUMBER # Atom | object # ObjectValue | array # ArrayValue | 'true' # Atom | 'false' # Atom | 'null' # Atom ; LCURLY : '{' ; LBRACK : '[' ; STRING : '"' (ESC | ~["\\])* '"' ; fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ; fragment UNICODE : 'u' HEX HEX HEX HEX ; fragment HEX : [0-9a-fA-F] ; NUMBER : '-'? INT '.' INT EXP? // 1.35, 1.35E-9, 0.3, -4.5 | '-'? INT EXP // 1e10 -3e4 | '-'? INT // -3, 45 ; fragment INT : '0' | '1'..'9' '0'..'9'* ; // no leading zeros fragment EXP : [Ee] [+\-]? INT ; // \- since - means "range" inside [...] WS : [ \t\n\r]+ -> skip ;
上面是依照antlr4的语法格式编辑的文件ide
antlr4文件语法也比较简单:函数
涉及到的几个专有名词:工具
# antlr4 -Dlanguage=Go -package json2xml JSON.g4
使用antlr生成目标语言为Go, package名为json2xml的基础代码
生成的文件以下:ui
$ tree ├── JSON.g4 ├── JSON.interp # 语法解析中间文件 ├── JSON.tokens # 语法分析tokens流文件 ├── JSONLexer.interp # 词法分析中间文件 ├── JSONLexer.tokens # 词法分析tokens流文件 ├── json_base_listener.go # 默认是listener模式文件 ├── json_lexer.go # 词法分析器 ├── json_listener.go # 抽象listener接口文件 ├── json_parser.go # parser解析器文件
package main import ( "fmt" "io/ioutil" "log" "os" "strings" "testing" "c2j/parser/json2xml" "github.com/antlr/antlr4/runtime/Go/antlr" ) func init() { log.SetFlags(log.LstdFlags | log.Lshortfile) } type j2xConvert struct { *json2xml.BaseJSONListener xml map[antlr.Tree]string } func NewJ2xConvert() *j2xConvert { return &j2xConvert{ &json2xml.BaseJSONListener{}, make(map[antlr.Tree]string), } } func (j *j2xConvert) setXML(ctx antlr.Tree, s string) { j.xml[ctx] = s } func (j *j2xConvert) getXML(ctx antlr.Tree) string { return j.xml[ctx] } // j2xConvert methods func (j *j2xConvert) ExitJson(ctx *json2xml.JsonContext) { j.setXML(ctx, j.getXML(ctx.GetChild(0))); } func (j *j2xConvert) stripQuotes(s string) string { if s == "" || ! strings.Contains(s, "\"") { return s } return s[1 : len(s)-1] } func (j *j2xConvert) ExitAnObject(ctx *json2xml.AnObjectContext) { sb := strings.Builder{} sb.WriteString("\n") for _, p := range ctx.AllPair() { sb.WriteString(j.getXML(p)) } j.setXML(ctx, sb.String()) } func (j *j2xConvert) ExitNullObject(ctx *json2xml.NullObjectContext) { j.setXML(ctx, "") } func (j *j2xConvert) ExitArrayOfValues(ctx *json2xml.ArrayOfValuesContext) { sb := strings.Builder{} sb.WriteString("\n") for _, p := range ctx.AllValue() { sb.WriteString("<element>") sb.WriteString(j.getXML(p)) sb.WriteString("</element>") sb.WriteString("\n") } j.setXML(ctx, sb.String()) } func (j *j2xConvert) ExitNullArray(ctx *json2xml.NullArrayContext) { j.setXML(ctx, "") } func (j *j2xConvert) ExitPair(ctx *json2xml.PairContext) { tag := j.stripQuotes(ctx.STRING().GetText()) v := ctx.Value() r := fmt.Sprintf("<%s>%s</%s>\n", tag, j.getXML(v), tag) j.setXML(ctx, r) } func (j *j2xConvert) ExitObjectValue(ctx *json2xml.ObjectValueContext) { j.setXML(ctx, j.getXML(ctx.Object())) } func (j *j2xConvert) ExitArrayValue(ctx *json2xml.ArrayValueContext) { j.setXML(ctx, j.getXML(ctx.Array())) } func (j *j2xConvert) ExitAtom(ctx *json2xml.AtomContext) { j.setXML(ctx, ctx.GetText()) } func (j *j2xConvert) ExitString(ctx *json2xml.StringContext) { j.setXML(ctx, j.stripQuotes(ctx.GetText())) } func TestJSON2XMLVisitor(t *testing.T) { f, err := os.Open("testdata/json2xml/t.json") if err != nil { panic(err) } defer f.Close() content, err := ioutil.ReadAll(f) if err != nil { panic(err) } // Setup the input is := antlr.NewInputStream(string(content)) // Create lexter lexer := json2xml.NewJSONLexer(is) stream := antlr.NewCommonTokenStream(lexer, antlr.LexerDefaultTokenChannel) // Create parser and tree p := json2xml.NewJSONParser(stream) p.BuildParseTrees = true tree := p.Json() // Finally AST tree j2x := NewJ2xConvert() antlr.ParseTreeWalkerDefault.Walk(j2x, tree) log.Println(j2x.getXML(tree)) }
上面代码比较简单,看注释就好;
通常流程以下:翻译
其中针对中间生成的参数和结果如何存放?好办,直接定义个map,map键以Tree存放;
xml map[antlr.Tree]string
antlr生成的代码有两种默认,默认是listener实现,要生成visitor,须要另加参数-visitor。
这两种机制的区别在于,监听器的方法会被antlr提供的遍历器对象自动调用,而visitor模式的方法中,必须显示调用visit方法来访问子节点。若是忘记调用的话,对应的子树就不会被访问。
antlr是一个强大的工具,能让常见的语法解析工做事半功倍,效率极高。同时,该工具使语法分析过程和程序自己高度分离,提供足够的灵活性和可操控性。