我将向您演示一段非常小的 XQuery 的 BNF 子集,这是 XML 的查询语言的 W3C 规范(请参阅 参考资料)。我在这里所说的大多数内容也适用于 XPath,因为这两者共享了许多相同的语法。我还将简要地研究运算符优先级的问题,并将树遍历代码推广到成熟的递归例程中,该例程可以处理任意复杂的解析树。
[21] Query ::= QueryProlog QueryBody ... [23] QueryBody ::= ExprSequence? [24] ExprSequence ::= Expr ( "," Expr )* [25] Expr ::= OrExpr ... [35] RangeExpr ::= AdditiveExpr ( "to" AdditiveExpr )* [36] AdditiveExpr ::= MultiplicativeExpr (("+" | "-") MultiplicativeExpr )* [37] MultiplicativeExpr ::= UnionExpr (("*" | "div" | "idiv" | "mod") UnaryExpr )* ... |
清单 4 显示了 .jjt 脚本。请注意该文件顶部的 options{}
块。这些选项(还有许多其它可用选项开关)指定了其中树构建在本例中是以 多重 方式运行的,即节点构造器用于显式地命名所生成节点的类型。备用方法(不在这里研究)是结果只将 SimpleNode
节点提供给解析树,而不是它的子类。如果您想要避免扩散节点类,那么该选项很有用。
请注意原始的 XQuery BNF 经常将多个运算符组合到同一个结果中。在 清单 4中,我已经将这些运算符分离到 JJTree 脚本中的单独结果中,因为这让客户机端的代码更简单。要进行组合,只需存储已扫描的运算符的值,就象对整数所进行的操作一样。
清单 4:清单 3 中的 XQuery 语法的 JJTree 脚本
options { MULTI=true; NODE_DEFAULT_VOID=true; NODE_PREFIX=""; } PARSER_BEGIN( XQueryParser ) package examples.example_2; public class XQueryParser{} PARSER_END( XQueryParser ) SimpleNode query() #Root : {} { additiveExpr() <EOF> { return jjtThis; }} void additiveExpr() : {} { subtractiveExpr() ( "+" subtractiveExpr() #Add(2) )* } void subtractiveExpr() : {} { multiplicativeExpr() ( "-" multiplicativeExpr() #Subtract(2) )* } void multiplicativeExpr() : {} { divExpr() ( "*" divExpr() #Mult(2) )* } void divExpr() : {} { integerLiteral() ( "div" integerLiteral() #Div(2) )* } void integerLiteral() #IntLiteral : { Token t; } { t=<INT> { jjtThis.setText(t.image); }} SKIP : { " " | "\t" | "\n" | "\r" } TOKEN : { < INT : ( ["0" - "9"] )+ > } |
该 .jjt 文件引入了几个新的特性。例如,该语法中的运算结果现在是 迭代的 :通过使用 *
(零次或多次)发生指示符来表示它们的可选的第二项,这与 清单 2 中的 ?
(零次或一次)表示法相反。该脚本所提供的解析器可以解析任意长的表达式,如“1 + 2 * 3 div 4 + 5”。
就象我曾答应的,我将用客户机端代码的清单作为总结,该清单将调用该解析器并遍历它生成的解析树,它使用简单而功能强大的递归 eval()
函数对树遍历时遇到的每个节点执行正确操作。 清单 5中的注释提供了关于内部 JJTree 工作的附加详细信息。
// return the arithmetic result of evaluating ‘query‘ public int parse( String query ) //------------------------------ { SimpleNode root = null; // instantiate the parser XQueryParser parser = new XQueryParser( new StringReader( query )); try { // invoke it via its topmost production // and get a parse tree back root = parser.query(); root.dump(""); } catch( ParseException pe ) { System.out.println( "parse(): an invalid expression!" ); } catch( TokenMgrError e ) { System.out.println( "a Token Manager error!" ); } // the topmost root node is just a placeholder; ignore it. return eval( (SimpleNode) root.jjtGetChild(0) ); } int eval( SimpleNode node ) //------------------------- { // each node contains an id field identifying its type. // we switch on these. we could use instanceof, but that‘s less efficient // enum values such as JJTINTLITERAL come from the interface file // SimpleParserTreeConstants, which SimpleParser implements. // This interface file is one of several auxilliary Java sources // generated by JJTree. JavaCC contributes several others. int id = node.id; // eventually the buck stops here and we unwind the stack if ( node.id == JJTINTLITERAL ) return Integer.parseInt( node.getText() ); SimpleNode lhs = (SimpleNode) node.jjtGetChild(0); SimpleNode rhs = (SimpleNode) node.jjtGetChild(1); switch( id ) { case JJTADD : return eval( lhs ) + eval( rhs ); case JJTSUBTRACT : return eval( lhs ) - eval( rhs ); case JJTMULT : return eval( lhs ) * eval( rhs ); case JJTDIV : return eval( lhs ) / eval( rhs ); default : throw new java.lang.IllegalArgumentException( "eval(): invalid operator!" ); } } |
如果您想要查看可以处理许多实际 XQuery 语法的功能更丰富的 eval()
函数版本,欢迎下载我的开放源码 XQuery 实现(XQEngine)的副本(请参阅 参考资料 )。它的 TreeWalker.eval()
例程 例举了 30 多种 XQuery 节点类型。还提供了一个 .jjt 脚本。