将解析树转换为XML

时间:2017-10-09 13:55:52

标签: java xml parsing antlr4

我有一个已编译的语法,我想用它将输入序列转换为XML。请注意,在我的情况下,我有一个非常大的语法,有很多规则,我想避免覆盖我的代码中的每个语法规则。

我将用一个例子来避免混淆。让我们有以下语法

grammar expr;

prog: stat+ ;

stat: expr NEWLINE
 | ID '=' expr NEWLINE
 | NEWLINE
;

expr:  expr ('*'|'/') expr
 | INT
 | ID
 | '(' expr ')'
;

ID : [a-zA-Z]+ ; // match identifiers
INT : [0-9]+ ; // match integers
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal)
WS : [ \t]+ -> skip ; // toss out whitespace

输入序列

A = 10
B = A * A

预期输出

<prog> 
    <stat> 
        A = 
        <expr> 10
        </expr> 
        \r\n
    </stat>  
    <stat> 
        B = 
        <expr>
            <expr>A</expr> 
            * 
            <expr> A</expr>
        </expr> 
        \r\n
    </stat>
</prog>

对应于解析树

enter image description here

目前我使用的方法是创建ParseTree并使用toStringTree方法生成以下字符串

(prog (stat A = (expr 10) \r\n) (stat B = (expr (expr A) * (expr A)) \r\n))

我随后将其转换为上面显示的XML(我使用简单的通用代码来处理任何语法)。我发现这种方法是假的。没有toStringTree可以解决它吗?我想避免覆盖访问者中的每个语法规则。 (我有数百个)。

编辑

我基本上需要某种通用的ParseTree序列化为XML格式。主要目标是我不必为每个规则在Java中编写特殊的序列化方法。

3 个答案:

答案 0 :(得分:3)

您可以使用ANTLR4的访问者功能。根据您使用的工具,您可能需要在生成类时添加-visitor command line parameter

为了更好地工作,我添加了一些labels to your parser rules

prog
 : stat+ EOF
 ;

stat
 : expr NEWLINE        #exprStat
 | ID '=' expr NEWLINE #assignStat
 | NEWLINE             #emptyStat
 ;

expr
 : lhs=expr op=('*'|'/') rhs=expr #multExpr
 | INT                            #intExpr
 | ID                             #idExpr
 | '(' expr ')'                   #nestedExpr
 ;

您的访问者可能如下所示:

public class XmlVisitor extends exprBaseVisitor<String> {

  @Override
  public String visitProg(exprParser.ProgContext ctx) {
    StringBuilder builder = new StringBuilder("<prog>");
    for (exprParser.StatContext stat : ctx.stat()) {
      builder.append(super.visit(stat));
    }
    return builder.append("</prog>").toString();
  }

  @Override
  public String visitExprStat(exprParser.ExprStatContext ctx) {
    return "expr";
  }

  @Override
  public String visitAssignStat(exprParser.AssignStatContext ctx) {
    return "<stat>" + ctx.ID() + " = " + super.visit(ctx.expr()) + "\\r\\n</stat>";
  }

  @Override
  public String visitEmptyStat(exprParser.EmptyStatContext ctx) {
    return "\\r\\n";
  }

  @Override
  public String visitMultExpr(exprParser.MultExprContext ctx) {
    return "<expr>" + super.visit(ctx.lhs) + ctx.op.getText() + super.visit(ctx.rhs) + "</expr>";
  }

  @Override
  public String visitIntExpr(exprParser.IntExprContext ctx) {
    return "<expr>" + ctx.INT().getText() + "</expr>";
  }

  @Override
  public String visitIdExpr(exprParser.IdExprContext ctx) {
    return "<expr>" + ctx.ID().getText() + "</expr>";
  }

  @Override
  public String visitNestedExpr(exprParser.NestedExprContext ctx) {
    return "<expr>" + super.visit(ctx.expr()) + "</expr>";
  }
}

要测试此访问者,请运行以下代码:

String source = "A = 10\nB = A * A\n";
exprLexer lexer = new exprLexer(CharStreams.fromString(source));
exprParser parser = new exprParser(new CommonTokenStream(lexer));
ParseTree tree = parser.prog();
String xml = new XmlVisitor().visit(tree);
System.out.println(xml);

将打印:

<prog><stat>A = <expr>10</expr>\r\n</stat><stat>B = <expr><expr>A</expr>*<expr>A</expr></expr>\r\n</stat></prog>

答案 1 :(得分:1)

这种方法可能适合您的需求。为了便于阅读,我用额外的标签t包裹终端符号,同时跳过带有空格的那些符号。然而,如果需要,调整输出应该不是一个大问题。

final exprLexer lexer = new exprLexer(CharStreams.fromString("A=10\nB = A * A\n"));
final CommonTokenStream tokens = new CommonTokenStream(lexer);
final exprParser parser = new exprParser(tokens);
final ParseTree tree = parser.prog();
ParseTreeWalker.DEFAULT.walk(new exprBaseListener()
{
    final String INDENT = "    ";
    int level = 0;
    @Override
    public void enterEveryRule(final ParserRuleContext ctx)
    {
        System.out.printf("%s<%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]);
        ++level;
        super.enterEveryRule(ctx);
    }

    @Override
    public void exitEveryRule(final ParserRuleContext ctx)
    {
        --level;
        System.out.printf("%s</%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]);
        super.exitEveryRule(ctx);
    }

    @Override
    public void visitTerminal(final TerminalNode node)
    {
        final String value = node.getText();
        if (!value.matches("\\s+"))
        {
            System.out.printf("%s<t>%s</t>%n", indent(), node.getText());
        }
        super.visitTerminal(node);
    }

    private String indent()
    {
        return String.join("", Collections.nCopies(level, INDENT));
    }
}, tree);

答案 2 :(得分:0)

我创建了一个ANTLR语法,它读取ParseTree.toStringTree方法生成的LISP样式树。项目可访问here。它有以下几部分

<强>语法

grammar str;

expr:
 STRING expr                                # exprString
 | LR_BRACKET expr RR_BRACKET expr          # exprParenthesis
 | LR_STRING_BRACKET expr RR_BRACKET expr   # exprRule 
 | <EOF>                                    # exprEnd
 | EOF_MARK                                 # exprEOF
 |                                          # exprEpsilon
;

EOF_MARK:            '<EOF>' ;
LR_STRING_BRACKET:  '(' ~[ ()]+;
LR_BRACKET:         '(';
RR_BRACKET:         ')';
STRING:             ~[ ()]+;
SPACE:              [ \t\r\n]+    -> skip; // toss out whitespace

<强> strXMLVisitor.java

public class strXMLVisitor extends strBaseVisitor<String> {

  @Override
  public String visitExprString(strParser.ExprStringContext ctx) 
  {
    return ctx.STRING().getText() + super.visit(ctx.expr());
  }

  @Override
  public String visitExprParenthesis(strParser.ExprParenthesisContext ctx) {
    return "(" + super.visit(ctx.expr(0)) + ")" + super.visit(ctx.expr(1));
  }

  @Override
  public String visitExprRule(strParser.ExprRuleContext ctx) {
    String value = ctx.LR_STRING_BRACKET().getText().substring(1);
    return "<" + value + ">" + super.visit(ctx.expr(0)) + "</" + value + ">" + super.visit(ctx.expr(1));
  }

  @Override
  public String visitExprEnd(strParser.ExprEndContext ctx) {
    return "";
  }

  @Override
  public String visitExprEOF(strParser.ExprEOFContext ctx) {
    return "";
  }

  @Override
  public String visitExprEpsilon(strParser.ExprEpsilonContext ctx) {
    return "";
  }  
}

<强> main.java

import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;

public class main {
    public static void main(String[] args) throws Exception {
       // create a CharStream that reads from standard input
       ANTLRInputStream input = new ANTLRInputStream(System.in);
       // create a lexer that feeds off of input CharStream
       strLexer lexer = new strLexer(input);
       // create a buffer of tokens pulled from the lexer
       CommonTokenStream tokens = new CommonTokenStream(lexer);
       // create a parser that feeds off the tokens buffer
       strParser parser = new strParser(tokens);
       ParseTree tree = parser.expr(); // begin parsing at init rule

       String xml = "<?xml version=\"1.0\"?>" + new strXMLVisitor().visit(tree);
       System.out.println(xml);      
    }
}

一旦你准备好antlr4(以及CLASSPATH中的引用),你可以使用以下命令来运行它:

 antlr4 -visitor str.g4
 javac *.java
 java main < file

文件必须包含LISP树格式的输入,结果是标准输出上的XML。