ANTLR4(五) JSON翻译成XML

191 阅读3分钟

预期效果

输入t.json
在这里插入图片描述
输出t.xml
在这里插入图片描述

原有语法文件

// Derived from http://json.org

grammar JSON;

json:   object
    |   array
    ;

object
    :   '{' pair (',' pair)* '}'    
    |   '{' '}'                     
    ;
pair:   STRING ':' value ;

array
    :   '[' value (',' value)* ']'  
    |   '[' ']'                     
    ;

value
    :   STRING  #String
    |   NUMBER  #Atom
    |   object  #ObjectValue
    |   array   #ArrayValue
    |   'true'  #Atom
    |   'false' #Atom
    |   'null'  #Atom
    ;

STRING :  '"' (ESC | ~["\])* '"' ;

fragment ESC :   '\' (["\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;

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 ;


123456789101112131415161718192021222324252627282930313233343536373839404142434445

我的思考

  1. 首先要区分JSON两种形式:object(键值对)和array。它们在xml中表现形式也是不同的。object是单纯的< tag > value < /tag >。而array需要将每个元素前后插入
    < element >和< /element >
  2. 如何实现嵌套,且object的嵌套与array本身相同,都需要有\t
  3. 何时去打印呢,是在enterexit时候吗。

现有语法文件

注意我们在object和array的不同子分支上,打上了不同的标签。

// Derived from http://json.org

grammar JSON;

json:   object
    |   array
    ;

object
    :   '{' pair (',' pair)* '}'    #AnObject
    |   '{' '}'                     #EmptyObject
    ;
pair:   STRING ':' value ;

array
    :   '[' value (',' value)* ']'  #ArrayOfValues
    |   '[' ']'                     #EmptyOfValues
    ;

value
    :   STRING  #String
    |   NUMBER  #Atom
    |   object  #ObjectValue
    |   array   #ArrayValue
    |   'true'  #Atom
    |   'false' #Atom
    |   'null'  #Atom
    ;

STRING :  '"' (ESC | ~["\])* '"' ;

fragment ESC :   '\' (["\/bfnrt] | UNICODE) ;
fragment UNICODE : 'u' HEX HEX HEX HEX ;
fragment HEX : [0-9a-fA-F] ;

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 ;

具体流程

针对上面的思考,我们拟出了一个方案。

  1. 首先我们不采用进入打印,退出打印这种一步一步的操作,而是通过ParseTreeProperty的辅助数据结构,将每个结点的值保存起来,在最后的exitJson里保存整个结构。这时一个由下而上的保存过程。

    观察下图,我们可以从最下层的value开始保存,value->pair->object->json。

    在这里插入图片描述 因此需要先创建一个辅助数据结构,并且定义get和set接口:

            ParseTreeProperty<String> xml=new ParseTreeProperty<String>();
    
            public void setXML(ParseTree ctx,String s){ xml.put(ctx,s); }
            public String getXML(ParseTree ctx){    return xml.get(ctx);    }
    
    
    12345
    
  2. 由下至上,我们先处理value的几个分支。NUMBER ‘true’ ‘false’ ‘null’它们可以创建同一个标签,处理函数就是exit时保存到xml即可。

            public void exitAtom(JSONParser.AtomContext ctx) {
                setXML(ctx,ctx.getText());
            }
    
  3. STRING的道理类似,但由于本身带有””,在被getText获取前需要手动去掉双引号,再保存到结点里。

            public static String stripQuotes(String s) {
                if ( s==null || s.charAt(0)!='"' ) return s;
                return s.substring(1, s.length() - 1);
            }
            
            public void exitString(JSONParser.StringContext ctx) {
                setXML(ctx,stripQuotes(ctx.getText()));
            }
    
    12345678
    
  4. object在这里我们虽然创建了新的标签,但是由于是嵌套的,它最终会调用AnObject或者EmptyObject标签存到xml中。所以我们直接setXML(ctx,getXML(ctx.object())即可。

            public void exitObjectValue(JSONParser.ObjectValueContext ctx) {
                setXML(ctx,getXML(ctx.object()));
            }
    
  5. array同理,我们直接setXML(ctx,getXML(ctx.array())即可。

            public void exitArrayValue(JSONParser.ArrayValueContext ctx) {
                setXML(ctx,getXML(ctx.array()));
            }
    
    
  6. value保存完以后,我们考虑pair该如何保存。很简单,其实就是
    < STRING>value< /STRING> 的格式,只不过要将标签的双引号给去掉,并且在最后加上\n。

            public void exitPair(JSONParser.PairContext ctx) {
                String tag=stripQuotes(ctx.STRING().getText());
                String x=String.format("<%s>%s</%s>\n", tag,getXML(ctx.value()),tag);
                setXML(ctx,x);
            }
    
  7. Pair保存完我们就回到了开始的json的两个分支,首先是object。很简单,创建一个String构造器,将pair的值一个个赋给它。

        public void exitAnObject(JSONParser.AnObjectContext ctx) {
            StringBuilder buf=new StringBuilder();
            buf.append('\n');
            for(JSONParser.PairContext v:ctx.pair()){
                buf.append(getXML(v));
            }
            setXML(ctx,buf.toString());
        }
    
    
  8. 数组与object略微不同的地方在于,每个元素前需要加< element>
    后需要加< /element >,插入value的时候注意点就好。

            public void exitArrayOfValues(JSONParser.ArrayOfValuesContext ctx) {
                StringBuilder buf=new StringBuilder();
                buf.append('\n');
                for(JSONParser.ValueContext v:ctx.value()){
                    buf.append("<element>");
                    buf.append(getXML(v));
                    buf.append("</element>");
                    buf.append('\n');
                }
                setXML(ctx,buf.toString());
            }
    
  9. 后来到json部分,我们将json的第一个孩子结点的内容传给它就算完事了。

            public void exitJson(JSONParser.JsonContext ctx) {
                setXML(ctx,getXML(ctx.getChild(0)));
            }
    

运行结果

如何将嵌套造成的缩进效果显式出来是一个问题,我们后续会对其解决。

在这里插入图片描述