javascript
Antlr4入门(六)实战之JSON
本章中,我們將學(xué)習(xí)編寫(xiě)JSON語(yǔ)法文件,即如何通過(guò)閱讀參考手冊(cè)、樣例代碼和已有的非ANTLR語(yǔ)法來(lái)構(gòu)造完整的語(yǔ)法。接著我們將使用監(jiān)聽(tīng)器或訪問(wèn)器來(lái)將JSON格式轉(zhuǎn)成XML。
注:JSON是一種存儲(chǔ)鍵值對(duì)的數(shù)據(jù)結(jié)構(gòu),由于值本身也可以作為鍵值對(duì)的容器,所以JSON中可以包含嵌套結(jié)構(gòu)。
一、自頂向下的設(shè)計(jì)——編寫(xiě)JSON語(yǔ)法
在本章中,我們的目標(biāo)是通過(guò)閱讀JSON參考手冊(cè)、查看它的語(yǔ)法描述圖和現(xiàn)有的語(yǔ)法來(lái)構(gòu)造一個(gè)能夠解析JSON的ANTLR語(yǔ)法。下面,我們將從JSON參考手冊(cè)中提取關(guān)鍵詞匯,然后一步步將它們編寫(xiě)成ANTLR規(guī)則。
一個(gè)JSON文件可以是一個(gè)對(duì)象,或者是由若干個(gè)值組成的數(shù)組從語(yǔ)法上看,這不過(guò)是一個(gè)選擇模式,因此,我們可以使用下列規(guī)則來(lái)表達(dá):
// 一個(gè)JSON文件可以是一個(gè)對(duì)象,或者是由若干個(gè)值組成的數(shù)組 json : object| array;下一步是將json規(guī)則引用的各個(gè)子規(guī)則進(jìn)行分解。對(duì)于對(duì)象,JSON語(yǔ)法是這樣定義的:
一個(gè)對(duì)象是一組無(wú)序的鍵值對(duì)集合。一個(gè)對(duì)象以一個(gè)左花括號(hào){開(kāi)始,且以右花括號(hào)}結(jié)束。每個(gè)鍵后跟一個(gè)冒號(hào):,鍵值對(duì)之間由逗號(hào),分隔。JSON官網(wǎng)上的語(yǔ)法圖強(qiáng)調(diào)對(duì)象中的鍵必須是字符串。為將上面這段自然語(yǔ)言的表述轉(zhuǎn)換成語(yǔ)法結(jié)構(gòu),我們?cè)囍鴮⑺纸?#xff0c;從中提取關(guān)鍵的、能夠指示采用何種模式的詞組。第一句話(huà)中的“一個(gè)對(duì)象是”明確地告訴我們創(chuàng)建一個(gè)名為“object”的規(guī)則。接著,“一組無(wú)序的鍵值對(duì)集合”實(shí)際上是若干個(gè)“鍵值對(duì)”組成的序列。而“無(wú)序的集合”指明了對(duì)象的鍵的語(yǔ)義,即鍵的順序沒(méi)有意義。第二個(gè)句子中引入了一個(gè)詞法符號(hào)依賴(lài),一個(gè)對(duì)象是以左右花括號(hào)作為開(kāi)始和結(jié)束的。最后一個(gè)句子進(jìn)一步指明了鍵值對(duì)序列的細(xì)節(jié):由逗號(hào)分隔。至此,我們可以得到以下ANTLR標(biāo)記編寫(xiě)的語(yǔ)法:
// 一個(gè)對(duì)象是一組無(wú)序的鍵值對(duì)集合。一個(gè)對(duì)象以一個(gè)左花括號(hào){開(kāi)始,且以右花括號(hào)}結(jié)束。 // 每個(gè)鍵后跟一個(gè)冒號(hào):,鍵值對(duì)之間由逗號(hào),分隔 object : '{' pair (',' pair)* '}' | '{' '}' ; pair : STRING ':' value;下面,我們接著來(lái)看JSON中另外一種高級(jí)結(jié)構(gòu)——數(shù)組。數(shù)組的語(yǔ)法描述如下:
數(shù)組是一組值的有序集合。一個(gè)數(shù)組由一個(gè)左方括號(hào)[開(kāi)始,并以一個(gè)右方括號(hào)]結(jié)束。其中的值由逗號(hào),分隔和object規(guī)則一樣,array包含一個(gè)由逗號(hào)分隔的序列模式和一個(gè)左右方括號(hào)間的詞法符號(hào)依賴(lài)。
// 數(shù)組是一組值的有序集合。一個(gè)數(shù)組由一個(gè)左方括號(hào)[開(kāi)始,并以一個(gè)右方括號(hào)]結(jié)束。 // 其中的值由逗號(hào),分隔 array : '[' value (',' value)* ']'| '[' ']';在上訴規(guī)則的基礎(chǔ)上進(jìn)一步細(xì)分,我們就需要編寫(xiě)規(guī)則value。通過(guò)查看JSON參考手冊(cè),我們可以知道value的語(yǔ)法描述如下:
一個(gè)值可以是一個(gè)雙引號(hào)包圍的字符串、一個(gè)數(shù)字、true\false、null、一個(gè)對(duì)象、或者一個(gè)數(shù)組。顯而易見(jiàn),這是一個(gè)很簡(jiǎn)單的選擇模式。
// 一個(gè)值可以是一個(gè)雙引號(hào)包圍的字符串、一個(gè)數(shù)字、true\false、null、一個(gè)對(duì)象、或者一個(gè)數(shù)組。 value : STRING| NUMBER| 'true'| 'false'| 'null'| object| array;這里,由于value規(guī)則引用了object和array,它成為(間接)遞歸規(guī)則。以上就是解析JSON的所有語(yǔ)法規(guī)則,下面我們來(lái)看下詞法規(guī)則。
根據(jù)JSON語(yǔ)法參考,字符串定義如下:
一個(gè)字符串就是一個(gè)由零個(gè)或多個(gè)Unicode字符組成的序列,它由雙引號(hào)包圍,其中的Unicode字符使用反斜杠轉(zhuǎn)義。單個(gè)字符由長(zhǎng)度為1的字符串表示。JSON的字符串定義和C/Java中的字符串非常相似。其實(shí)在前文中,我們已經(jīng)編寫(xiě)了字符串的ANTLR詞法規(guī)則,而這里的JSON字符串定義只是比我們之前編寫(xiě)的字符串增加了對(duì)Unicode字符的轉(zhuǎn)義。我們接著查看JSON參考手冊(cè),可以得到以下需要被轉(zhuǎn)義的字符。
因此,我們的string規(guī)則定義如下:
// 一個(gè)字符串就是一個(gè)由零個(gè)或多個(gè)Unicode字符組成的序列,它由雙引號(hào)包圍,其中的字符使用反斜杠轉(zhuǎn)義。 // 單個(gè)字符由長(zhǎng)度為1的字符串表示 STRING : '"' (ESC | ~["\\])* '"'; fragment ESC : '\\' (["\\/bfnrt] | UNICODE); fragment UNICODE : 'u' HEX HEX HEX HEX; fragment HEX : [0-9a-fA-F];其中ESC片段規(guī)則匹配一個(gè)Unicode序列或者預(yù)定義的轉(zhuǎn)義字符。而在UNICODE片段規(guī)則中,我們又定義了一個(gè)HEX片段規(guī)則來(lái)替代需要多次重復(fù)的編寫(xiě)的十六進(jìn)制數(shù)字。
最后一個(gè)需要編寫(xiě)的詞法符號(hào)是NUMBER。
// 一個(gè)數(shù)字和C/Java中的數(shù)字非常相似,除了一點(diǎn)之外:不允許使用八進(jìn)制和十六進(jìn)制 NUMBER: '-'? INT '.' [0-9]+ EXP? // 1.35, 1.35E-9, 0.3, -4.5| '-'? INT EXP // 1e10 -3e4| '-'? INT // -3, 45; fragment INT : '0' | [1-9] [0-9]* ; // 除零外的數(shù)字不允許以0開(kāi)始 fragment EXP : [Ee] [+\-]? INT ; // \- 是對(duì)-的轉(zhuǎn)義,因?yàn)閇...]中的-用于表示“范圍”和上一章CSV語(yǔ)法中不同的是,JSON需要額外處理空白字符。
WS : [ \t\n\r]+ -> skip ;至此,完整的JSON語(yǔ)法文件已經(jīng)編寫(xiě)完畢。下面是完整的JSON語(yǔ)法文件并為備選分支添加標(biāo)簽后的結(jié)果:
grammar JSON;// 一個(gè)JSON文件可以是一個(gè)對(duì)象,或者是由若干個(gè)值組成的數(shù)組 json : object| array;// 一個(gè)對(duì)象是一組無(wú)序的鍵值對(duì)集合。一個(gè)對(duì)象以一個(gè)左花括號(hào){開(kāi)始,且以右花括號(hào)}結(jié)束。 // 每個(gè)鍵后跟一個(gè)冒號(hào):,鍵值對(duì)之間由逗號(hào),分隔 object : '{' pair (',' pair)* '}' #AnObject| '{' '}' #EmptyObject //空對(duì)象; pair : STRING ':' value;// 數(shù)組是一組值的有序集合。一個(gè)數(shù)組由一個(gè)左方括號(hào)[開(kāi)始,并以一個(gè)右方括號(hào)]結(jié)束。 // 其中的值由逗號(hào),分隔 array : '[' value (',' value)* ']' #ArrayOfValues| '[' ']' #EmptyArray //空數(shù)組;// 一個(gè)值可以是一個(gè)雙引號(hào)包圍的字符串、一個(gè)數(shù)字、true\false、null、一個(gè)對(duì)象、或者一個(gè)數(shù)組。 value : STRING #String| NUMBER #Atom| 'true' #Atom| 'false' #Atom| 'null' #Atom| object #ObjectValue| array #ArrayValue;// 一個(gè)字符串就是一個(gè)由零個(gè)或多個(gè)Unicode字符組成的序列,它由雙引號(hào)包圍,其中的字符使用反斜杠轉(zhuǎn)義。 // 單個(gè)字符由長(zhǎng)度為1的字符串表示 STRING : '"' (ESC | ~["\\])* '"'; fragment ESC : '\\' (["\\/bfnrt] | UNICODE); fragment UNICODE : 'u' HEX HEX HEX HEX; fragment HEX : [0-9a-fA-F];// 一個(gè)數(shù)字和C/Java中的數(shù)字非常相似,除了一點(diǎn)之外:不允許使用八進(jìn)制和十六進(jìn)制 NUMBER: '-'? INT '.' [0-9]+ 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 ;讓我們使用ANTLR工具來(lái)測(cè)試下吧。
二、將JSON轉(zhuǎn)成XML
在本小節(jié)中我們將構(gòu)建一個(gè)從JSON到XML的翻譯器。對(duì)于以下JSON輸入,我們期待的輸出是:
其中,<element>元素是一個(gè)我們需要在翻譯過(guò)程中生成的標(biāo)簽。
由于監(jiān)聽(tīng)器無(wú)法存儲(chǔ)值(返回類(lèi)型是void),所以我們需要ParseTreeProperty來(lái)存放中間結(jié)果。
接著我們從最簡(jiǎn)單規(guī)則的開(kāi)始翻譯。value規(guī)則中的Atom備選分支用于匹配詞法符號(hào)中的文本內(nèi)容,對(duì)于它,我們只需要將值存入ParseTreeProperty即可。
@Overridepublic void exitAtom(JSONParser.AtomContext ctx) {setXml(ctx, ctx.getText());}而對(duì)于string,我們需要做一個(gè)額外處理——剔除首位雙引號(hào)。
@Overridepublic void exitArrayOfValues(JSONParser.ArrayOfValuesContext ctx) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n");for (JSONParser.ValueContext valueContext : ctx.value()){stringBuilder.append("<element>");stringBuilder.append(getXml(valueContext));stringBuilder.append("<element>");stringBuilder.append("\n");}setXml(ctx,stringBuilder.toString());}@Overridepublic void exitString(JSONParser.StringContext ctx) {setXml(ctx, stripQuotes(ctx.getText()));}而對(duì)于value規(guī)則的ObjectValue和ArrayValue備選分支,其實(shí)只需要去調(diào)用object和array規(guī)則方法就行。
@Overridepublic void exitObjectValue(JSONParser.ObjectValueContext ctx) {// 類(lèi)比 String value() { return object(); }setXml(ctx,getXml(ctx.object()));}@Overridepublic void exitArrayValue(JSONParser.ArrayValueContext ctx) {setXml(ctx,getXml(ctx.array()));}在完成對(duì)value規(guī)則所有元素的翻譯后,我們需要處理鍵值對(duì),將它們轉(zhuǎn)換成標(biāo)簽和文本。對(duì)于STRING ':' value,分別對(duì)應(yīng)XML中標(biāo)簽名和標(biāo)簽值。因此,它們的翻譯結(jié)果如下:
@Overridepublic void exitPair(JSONParser.PairContext ctx) {String tag = stripQuotes(ctx.STRING().getText());String value = String.format("<%s>%s<%s>\n",tag,getXml(ctx.value()),tag);setXml(ctx,value);}而對(duì)于object規(guī)則,我們知道它是由一系列的鍵值對(duì)組成,也就是說(shuō),我們需要循環(huán)遍歷其中的鍵值對(duì),將其對(duì)應(yīng)的XML追加到語(yǔ)法分析樹(shù)存儲(chǔ)的結(jié)果中。
@Overridepublic void exitAnObject(JSONParser.AnObjectContext ctx) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n");for (JSONParser.PairContext pairContext : ctx.pair()){stringBuilder.append(getXml(pairContext));}setXml(ctx,stringBuilder.toString());}@Overridepublic void exitEmptyObject(JSONParser.EmptyObjectContext ctx) {setXml(ctx,"");}同理,對(duì)于array規(guī)則,我們采用同樣的處理方式,唯一不同的是,我們需要為子節(jié)點(diǎn)添加標(biāo)簽<element>
@Overridepublic void exitArrayOfValues(JSONParser.ArrayOfValuesContext ctx) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n");for (JSONParser.ValueContext valueContext : ctx.value()){stringBuilder.append("<element>");stringBuilder.append(getXml(valueContext));stringBuilder.append("<element>");stringBuilder.append("\n");}setXml(ctx,stringBuilder.toString());}@Overridepublic void exitEmptyArray(JSONParser.EmptyArrayContext ctx) {setXml(ctx,"");}最后,我們將最終結(jié)果存入根節(jié)點(diǎn)中。
@Overridepublic void exitJson(JSONParser.JsonContext ctx) {setXml(ctx,getXml(ctx.getChild(0)));}完整的翻譯器代碼如下:
package json;import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeProperty;public class JSONToXMLListener extends JSONBaseListener {// 將每棵子樹(shù)翻譯完的字符串存儲(chǔ)在該子樹(shù)的根節(jié)點(diǎn)中private ParseTreeProperty<String> xml = new ParseTreeProperty<String>();public void setXml(ParseTree node, String value){xml.put(node, value);}public String getXml(ParseTree node){return xml.get(node);}/*** 去掉字符串首尾的雙引號(hào)""* @param s* @return*/public String stripQuotes(String s) {if ( s==null || s.charAt(0)!='"' ) return s;return s.substring(1, s.length() - 1);}@Overridepublic void exitJson(JSONParser.JsonContext ctx) {setXml(ctx,getXml(ctx.getChild(0)));}@Overridepublic void exitAnObject(JSONParser.AnObjectContext ctx) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n");for (JSONParser.PairContext pairContext : ctx.pair()){stringBuilder.append(getXml(pairContext));}setXml(ctx,stringBuilder.toString());}@Overridepublic void exitEmptyObject(JSONParser.EmptyObjectContext ctx) {setXml(ctx,"");}@Overridepublic void exitPair(JSONParser.PairContext ctx) {String tag = stripQuotes(ctx.STRING().getText());String value = String.format("<%s>%s<%s>\n",tag,getXml(ctx.value()),tag);setXml(ctx,value);}@Overridepublic void exitArrayOfValues(JSONParser.ArrayOfValuesContext ctx) {StringBuilder stringBuilder = new StringBuilder();stringBuilder.append("\n");for (JSONParser.ValueContext valueContext : ctx.value()){stringBuilder.append("<element>");stringBuilder.append(getXml(valueContext));stringBuilder.append("<element>");stringBuilder.append("\n");}setXml(ctx,stringBuilder.toString());}@Overridepublic void exitEmptyArray(JSONParser.EmptyArrayContext ctx) {setXml(ctx,"");}@Overridepublic void exitString(JSONParser.StringContext ctx) {setXml(ctx, stripQuotes(ctx.getText()));}@Overridepublic void exitAtom(JSONParser.AtomContext ctx) {setXml(ctx, ctx.getText());}@Overridepublic void exitObjectValue(JSONParser.ObjectValueContext ctx) {// 類(lèi)比 String value() { return object(); }setXml(ctx,getXml(ctx.object()));}@Overridepublic void exitArrayValue(JSONParser.ArrayValueContext ctx) {setXml(ctx,getXml(ctx.array()));} }編寫(xiě)main方法調(diào)用測(cè)試
import json.JSONLexer; import json.JSONParser; import json.JSONToXMLListener; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker;import java.io.BufferedReader; import java.io.FileReader;public class JSONMain {public static void main(String[] args) throws Exception{BufferedReader reader = new BufferedReader(new FileReader("xxx\\json.txt"));ANTLRInputStream inputStream = new ANTLRInputStream(reader);JSONLexer lexer = new JSONLexer(inputStream);CommonTokenStream tokenStream = new CommonTokenStream(lexer);JSONParser parser = new JSONParser(tokenStream);ParseTree parseTree = parser.json();System.out.println(parseTree.toStringTree());ParseTreeWalker walker = new ParseTreeWalker();JSONToXMLListener listener = new JSONToXMLListener();walker.walk(listener, parseTree);String xml = listener.getXml(parseTree);System.out.println(xml);} }json.txt內(nèi)容如下:
{"id" : 1,"name" : "Li","scores" : {"Chinese" : "95","English" : "85"},"array" : [1.2, 2.0e1, -3] }運(yùn)行結(jié)果如下:
后記
本章我們學(xué)習(xí)了如何通過(guò)閱讀參考手冊(cè)、采用自頂向下設(shè)計(jì)來(lái)編寫(xiě)JSON語(yǔ)法文件。還學(xué)習(xí)了使用監(jiān)聽(tīng)器來(lái)實(shí)現(xiàn)從JSON到XML的翻譯器。可以看到,我們翻譯的過(guò)程并不是一蹴而就的,而是采用分而治之的思想,是從最簡(jiǎn)單的開(kāi)始翻譯,然后將局部結(jié)果合并的。
?
總結(jié)
以上是生活随笔為你收集整理的Antlr4入门(六)实战之JSON的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 阿里巴巴常用的12个开发工具
- 下一篇: 原生JS面试题