【问题标题】:Transform parse tree into XML将解析树转换为 XML
【发布时间】:2018-03-20 18:24:34
【问题描述】:

我有一个已编译的语法,我想用它来将输入序列转换为 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>

对应一个解析树

目前我使用一种方法来创建ParseTree 并使用toStringTree 方法生成以下字符串

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

我随后将其转换为上面显示的 XML(我使用适用于任何语法的简单通用代码)。我觉得这种方法很愚蠢。没有toStringTree可以解决吗?我想避免覆盖我的访客中的每个语法规则。 (我有数百个)。

编辑

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

【问题讨论】:

  • 使用静态方法printXml(ParseTree tree) 在 ParseTree 上进行递归下降打印怎么样?
  • @StefanHaustein 我会检查一下,谢谢。我对 Antlr 没有太多经验。
  • 生成 XML 并不是一个好主意。你将如何操纵它? XSLT 在大型树上并不是很好,对于中等大小的 Java 程序,您会得到巨大的树。更糟糕的是,XSLT 不擅长上下文相关检查,并且所有的 java 构造都是上下文相关的(变量的含义取决于它们的声明)。查看解析后的生活,了解为什么您想要的不仅仅是 AST:semdesigns.com/Products/DMS/LifeAfterParsing.html
  • @IraBaxter 我的树将很小(几十到几百个元素),我计划使用 XQuery 引擎进行搜索和操作。
  • @RadimBača:好的,小树......也许 XML 本身不会淹没你。但是根据语言的上下文敏感性质对这些树进行认真的操作...... XQuery 不会很好地工作。解析后阅读生活点滴。

标签: java xml parsing tree antlr4


【解决方案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】:

您可以利用 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>

【讨论】:

  • 非常感谢您的努力。但是,请注意我的最后一句话“我想避免覆盖访问者中的每个语法规则。(我有数百个)。”。此外,如果语法发生变化,则必须适当地更改访问者,这在我当前的解决方案中是不必要的。
  • 如果您的语法规则之一发生变化,您将始终需要更改其他内容。无论是在访问者中,还是在您编写的静态方法中。我认为这是解决您的问题的“最佳”(最干净)方法,这就是我发布它的原因。随意使用或不使用它:我相信它会使某人受益。当然,不客气! :)
  • 检查树并发出节点的静态方法将(可能)变成一大堆意大利面条代码。在访问者中,每个节点都以自己的方法发出,保持整洁。也很容易进行单元测试!
  • 我同意你关于静态方法的看法。我有一种感觉,我对toStringTree 结果的解析是目前解决该问题的最简单、最通用的解决方案。我现在真的不想为所有语法规则写一个访问者(因为它们可能真的很多)。
  • 必须标记 ANTLR 节点,以便区分它们。因此,生成 XML 相当简单:遍历树、检查节点、确定其类型、为该根及其(递归)子生成 XML。 ANTLR 可以打印类似 LISP 的版本这一事实告诉您生成 XML 等价物必须很容易实现;只需窃取该代码并将其破解即可。 (我们以这种方式从我们的 DMS 软件再工程工具包中生成 XML。大约需要 50 行代码)。
【解决方案3】:

我创建了一个 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 "";
  }  
}

ma​​in.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-tree 格式的输入,结果是标准输出上的 XML。

【讨论】:

    猜你喜欢
    • 2016-09-20
    • 2017-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多