IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

应用二叉树解析 XML 表示的函数计算表达式

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


李 伟 (dragonli@cn.ibm.com)IBM

2008 年 1 月 28 日

本文主要介绍如何建立和应用二叉树的数据结构,来解析 XML 表示的函数计算表达式,在中序遍历中解析并可应用相关的逻辑来处理函数计算表达式。

XForms 的函数计算表达式简介

XForms 是用于 XML 数据处理的 Web 表单规范 , 它允许您将表单的用途和外观分开。目前 W3C 组织正在审查 XForms 1.1 的候选工作草案(1.0 是正式的 Internet 推荐标准)。IBM® Lotus Forms ( 电子表单领域的杰出产品之一 ) 就是基于 XForms 和 XFDL (Extensible Forms Description Language) 语言的,它把 XForms 强大的数据处理能力和 XFDL 语言丰富的表示能力紧密的结合在一起。

XForms 的数据模型(Data Model)封装了对表单数据的一些逻辑处理操作。如清单 1 所示,xforms:bind 元素拥有一个 calculate 属性。该属性通过函数计算表达式来实现表单数据的处理逻辑。


清单 1. XForms 的函数计算表达式
                
<xforms:model …>
 <xforms:instance>…</xforms:instance>
 …
 <xforms:bind calculate="../SubTotal + ../TaxAmount + ../Shipping" 
 nodeset="instance('INSTANCE')/P001/Total"></xforms:bind> 
 <xforms:bind calculate="if(../BudgetMeals !='',../BudgetMeals, '0')- ../ActualMeals"
 nodeset="instance('INSTANCE')/P001/VarianceMeals"></xforms:bind>
 <xforms:bind calculate="min(../aAccount , ../bAccount , ../ cAccount) " 
 nodeset="instance('INSTANCE')/P001/minAccount"></xforms:bind> 
 …
</xforms:model>

诸多领域,特别是电子商务领域,存在很多基于 XML 语言的产品。企业集成,数据交换以及转换工具等都涉及 XML2XML 的转换工作。那么如何解析转换另一种 XML 表示的函数计算表达式呢?本文试图解决这一问题。





回页首


简单介绍另一种的 XML 表示的函数计算表达式

在清单 2 中,我们简单介绍了另一种 XML 表示的函数计算表达式,其应用于一款基于 XML 语法的电子表单产品中。窥豹可见一斑,本文同样适用于其它基于 XML 语法的函数计算表达式。


清单 2. XForms 的函数计算表达式
                
…
<calculation>
 <func name="IfThen">
 <func name="Less">
 <cell name="DateTo" /> 
 <cell name="DateFrom" /> 
 </func>
 <func name="Alert">
 <boolean value="False" /> 
 <string value="This date must come after the date you have entered in the Date 
 From field" /> 
 </func>
 </func>
 <plainText>If DateTo < DateFrom Then False With Alert
  "This date must come after the  date you have entered in the Date From field"
   End</plainText> 
</calculation>
<calculation>
 <func name="Min">
 <func name="Abs">
 <cell name="Summary" /> 
 </func>
 <func name="Subtract">
 <cell name="Demo1">
 <cell name="Demo2">
 </func>
 <number value="100" /> 
 </func>
 <plainText>Min(Abs(Summary),Demo1-Demo2, 100)</plainText> 
</calculation>
…

在清单 2 中 <func> 标签用以标识函数或操作符。其 name 的属性值既是函数或操作符的名称。<cell> 标签,读者可以理解为用以标识函数计算表达式中的变量;而 <number>,<string> 等标签则是标识函数计算表达式中的常量内容。

当我们需要解析函数计算表达式的时候 , 我们都有什么办法呢?比如在上面的代码片段中,直接解析 <plainText> 元素的内容似乎是一个办法。直接解析 <plainText>Min(Abs(Summary), Demo1-Demo2, 100)</plainText> 就已经得到了正确的结果。然而,解析含有大量嵌套的条件判断语句的函数计算表达式,如解析第一个 <plainText> 元素中的内容就变得非常困难了。当我们要想进一步分析并处理 <plainText> 元素中的函数计算表达式的时候,表达式已经丢失了很多重要的原始信息(比如说类型信息),这样处理起来就难上加难了。有什么更好的办法么?我们可以尝试解析除了 <plainText> 元素以外的以 <calculation> 元素为根的 XML 代码片段中的信息。





回页首


建立用以解析函数计算表达式的二叉树数据结构模型

仔细分析一下清单 2 所示的函数计算表达式,我们不难得出其数据结构模型,如图 1 所示 :


图 1. 函数计算表达式的树模型
函数计算表达式的树模型

我们可以通过 XML 解析器(DOM, SAX, StAX etc.)解析 XML 得到基于以树为数据结构的内存模型。但是遍历以二叉树为数据结构的内存模型要比以树为数据结构的内存模型更方便明了(读者更为熟悉)。在图 2 中,我们把以树为数据结构的模型转换为对应的以二叉树为数据结构的模型(结点的孩子结点为该结点的左孩子结点;结点的兄弟结点为该结点的右孩子结点)。


图 2. 函数计算表达式的二叉树(BinTree)模型
函数计算表达式的二叉树(BinTree)模型

清单 3. XML 结点对应的内存模型
                
public class CalNode {
 public CalNode left;
 public CalNode right;
 private String value;
 private String type; // Three kinds of type: cell, func, const
 public CalNode(String type, String value) {
 this.type = type;
 this.value = value;
 }
 public CalNode(CalNode left, String type, String value, CalNode right) {
 this.left = left;
 this.type = type;
 this.value = value;
 this.right = right;
 }
 public String getValue() {
 return value;
 }
 public void setValue(String value) {
 this.value = value;
 }
 public String getType() {
 return type;
 }
}

在清单 3 中,我们创建 XML 结点对应的内存模型类 —— CalNode。该类非常简单,包括左孩子结点,右孩子结点,结点本身的值以及结点的类型。根据这种函数计算表达式所涉及的 XML 元素的标签值(tag)结点的类型可分为三种:func,函数和操作符;cell,函数计算表达式中的变量 ; const, 表达式中所涉及的字符串或数值常量(当我们遍历二叉树的时候,我们需要根据结点的类型进行相应的逻辑处理。)拿上面的例子来说,当解析 <func name=”Min”> 的时候,就生成了一个值为 Min, 类型是 funccalNode 类的实例对象。


清单 4. 应用 DOM 解析 XML 生成以二叉树为数据结构的内存模型
                
private CalNode createCalBinTree(Node root) {
 String rootName = ParserHelper.validateSID(XMLUtil.getAttribute(
(Element) root, Constants.ATTR_NAME));
 //For <number value=""/> 
 and <string value="xxxx"/> 
 elements within <func>
 if (StringHelper.isEmpty(rootName)) { //isEmpty 方法判断是否为空
 rootName = ParserHelper.validateComputeContent(XMLUtil.getAttribute(
 (Element) root, Constants.ATTR_VALUE));
 }
 String type = root.getNodeName(); // 结点的类型
 if (!CalConstants.CELL.equals(type) && !CalConstants.FUNC.equals(type))
 type = CalConstants.CONST; // 如果类型不是 func 或 cell,则设置为 const
 CalNode rootNode = new CalNode(null, type, rootName, null); // 生成 CalNode 类的实例对象
 Node firstNode = root.getFirstChild(); // 拿到结点 root 的第一个孩子(DOM API)
 if (firstNode != null){ 
 // 递归遍历第一个孩子结点,生成 CalNode 的实例对象。
 CalNode firstCalNode = createCalBinTree(firstNode);
 rootNode.left = firstCalNode; // 把第一个孩子结点设置为该结点的左孩子结点
// 考虑该结点的其它孩子结点
 Node pNode = firstNode;
CalNode qCalNode = firstCalNode;
while (pNode.getNextSibling() != null) {
 Node nextNode = pNode.getNextSibling();
 if (nextNode != null){ 
 CalNode nextCalNode = createCalBinTree(nextNode); // 递归遍历其它孩子结点
 qCalNode.right = nextCalNode; // 设置为其上一个兄弟结点的右孩子结点
 //loop for org.w3c.dom.Node
 pNode = nextNode; 
 //loop for CalNode 
 qCalNode = nextCalNode;
 }
 }
 }
 return rootNode;
}

如清单 4 所示,createCalBinTree 方法递归生成以二叉树为数据结构的内存模型(一棵 CalNode 类的实例对象树)。我们只要拿到对象树的根,就可以对其进行遍历处理了。





回页首


应用栈中序遍历并预处理函数计算表达式

清单 5 是一个使用栈的二叉树中序遍历算法。GoFarLeft 函数用来返回当前结点 t 的最左孩子结点。


清单 5. 二叉树中序非递归算法(应用栈)
                
private TreeNode<Elem > 
 GoFarLeft(TreeNode<Elem> t, Stack<TreeNode<Elem> > S) { 
 if (t == null) 
 return null; 
 while (t.left != null){
 S.push(t);
 t = t.left;
 }
 return t;
}
// inorder iterative scan
private void Inorder_I(TreeNode<Elem> t, void visit(Elem c)){ 
 Stack<TreeNode<Elem>> S=new Stack<TreeNode<Elem>>();
 t = GoFarLeft(t,S);
 // continue until t is NULL
 while(t != null){
 visit(t.data); 
 if (t.right!=null)
 t = GoFarLeft(t.right,S);
 else if ( !S.isEmpty( )) 
 t = S.pop();
 else
 t = null; // we are done
 }
}

在本文中,我们应用此算法预处理函数表达式。比如,我们想把函数表达式转换成 XForms 的函数表达式,而我们在遍历此二叉树的过程中,发现某一个函数在 XForms 规范中是不支持的,比如上面清单 1 中的 Alert 函数,那么我们就可以结束此此遍历操作。当然,更多的预处理情况是根据业务逻辑的实际需要。比如本文的函数转换逻辑,我们希望知道函数表达式是否包含些特殊的函数,比如说 Sum 函数,此函数的包含与否直接影响处理逻辑的实现。





回页首


中序非栈递归遍历并生成 XForms 函数计算表达式

在应用清单 5 的非递归算法预处理函数计算表达式之后,我们要应用中序遍历的递归算法真正处理和解析函数计算表达式。在解析的过程中,加入 XForms 函数计算表达式的相关逻辑,从而完成解析转换工作。在图 3 中,我们应用流程图来描述中序非栈递归遍历的解析处理过程。


图 3. 解析处理函数计算机表达式的流程图
解析处理函数计算机表达式的流程图

如上图所示,流程图根据 rootNode 的值是否为函数,操作符而分为三部分。第一部分就是对函数计算表达式中的函数进行处理。第二部分就是递归处理表达式中的操作符。最后处理表达式中的常量和变量。

其源代码如下:


清单 6. 中序非栈递归遍历并解析函数表达式
                
public void createXFormsBinding(CalNode rootNode, StringBuffer str) {
 // 参数 str 用来存放解析结果
 if (rootNode == null) { // 结点为空,返回
 return;
 }
 String foName = rootNode.getValue(); // 获取结点的值
 // 如果类型是 func , 并且是合法的函数
 if ( rootNode.getType().equals(CalConstants.FUNC) && 
 FunctionConstants.isValidFNFunction(foName)) {
 if (rootNode.left == null) {
 // 该结点的左孩子结点是空的时候,逻辑操作如下:
 if (FunctionConstants.NOW.equals(foName)) {
 str.append("now()"); 
 } else if (…) {
 …
 } 
 return;
 } else { // 该结点是带有参数的函数
 // 特殊函数的处理(如:IfThen 和 IFT)
 if (FunctionConstants.IFTHEN.equals(foName)
 || FunctionConstants.IFT.equals(foName)) {
 …
 return;
 }else if(…){ // 其它特殊的函数的处理逻辑
 …
 return;
 }else { // 非特殊函数的处理
 str.append(FunctionConstants.getMappedFunction(foName)
 + "("); //$NON-NLS-1$ // 加左括号
 // 递归调用,用以处理函数的参数
 createXFormsBinding(rootNode.left, str);
 }
 }
 // 右孩子信息
 CalNode p = rootNode.left;
 CalNode q = null;
 if(p!=null)
 q = rootNode.left.right;
 while (p != null && q != null) {
 str.append(","); //$NON-NLS-1$ // 参数用逗号分隔
 createXFormsBinding(q, str); // 递归调用,处理该参数
 q = q.right;
 }
 // 加右括号
 str.append(")"); //$NON-NLS-1$
 // 如果类型是 func , 并且是合法的操作符
 } else if (rootNode.getType().equals(CalConstants.FUNC) && 
 OperatorConstants.isValidFNOperator(foName)) {
 // 若没有左孩子结点,返回 
 if(rootNode.left==null){
 return;
 }
 // 如果是乘,除以及求模等操作符,需要进一步判断,根据需要对左右孩子树加括号
 if (OperatorConstants.DIV.equals(foName)
 || OperatorConstants.MULTIPLY.equals(foName)||…) {
 // 处理操作符的左孩子树
 if (OperatorConstants.isValidFNOperator(rootNode.left
 .getValue())&& …) {
 str.append("("); //$NON-NLS-1$
 createXFormsBinding(rootNode.left, str);
 str.append(")"); //$NON-NLS-1$
 } else {
 createXFormsBinding(rootNode.left, str);
 }
 // 取操作符的名字
 str.append(" " + 
 OperatorConstants.getMappedXFDLOperator(foName) + " ");
 // 处理操作符的右孩子树
 if (rootNode.left.right != null
 && OperatorConstants.isValidFNOperator(rootNode.left.right
 .getValue())&& …) {
 str.append("("); //$NON-NLS-1$
 createXFormsBinding(rootNode.left.right, str);
 str.append(")"); //$NON-NLS-1$
 } else {
 createXFormsBinding(rootNode.left.right, str);
 }
 }
 // 对 % 操作符的逻辑处理
 else if (OperatorConstants.PERCENT.equals(foName)) {
 …
 } 
 // 对其它特殊操作符的逻辑处理
 else if (OperatorConstants.XXX.equals(foName)||…) {
 …
 } 
 // 对非 func 类型进行处理(cell 类型以及表达式中的常量)
 else {
 if (rootNode.getType().equals(CalConstants.CELL)) {
 // 转成 XPATH 表达式的中结点
 str.append("../" + foName); //$NON-NLS-1$ 
 } else { // 表达式中的常量处理
 str.append("'" + foName + "'"); //$NON-NLS-1$ //$NON-NLS-2$
 }
 }
} 

在清单 6 中,我们比较详尽的给出了中序非栈递归遍历二叉树的算法,根据 CalNode 实例对象的不同类型分别进行处理。 func 类型既可能是函数,也可能是操作符。我们需要加以判断进而分别处理。在函数或操作符的处理逻辑中,又存在一些特殊的函数(比如说条件判断 IfThen 等函数)和一些特殊的操作符(比如 操作符),我们同样需要区分处理。对于 cell 类型的 CalNode 类的实例对象,他们是函数计算表达式中的变量,在解析的过程中需要根据实际的逻辑需要,执行相应的逻辑操作。本文中把其转换为 Xpath 的结点形式。比如表达式中 Summary 结点被转为: ../Summary。const 类型的常量的逻辑处理相对比较容易。在本文中,仅仅是把常量类型加上单引号。

如果执行此函数,Min(Abs(Summary),Demo1-Demo2, 100) 这样的函数计算表达式可能会被转换为 IBM® Lotus Forms 中如清单 7 所示的 XForms 绑定语句中 calculate 属性的值:


清单 7. 生成 XForms 的函数计算表达式
                
<xforms:bind calculate=" min (abs(../Summary), ../Demo1 - ../Demo2, ‘100’ )" 
 nodeset="instance('INSTANCE')/P001/mvNode"></xforms:bind>





回页首


小结

本文主要介绍了如何建立并应用二叉树的数据结构,遍历解析 XML 表示的函数计算表达式。在遍历的过程中,读者可以根据实际的需要加入相关的处理逻辑。本文涉及的用以演示的处理逻辑是函数计算表达式的转换,目的是把这种函数计算表达式转换为 IBM® Lotus Forms 产品所支持的函数计算表达式(即:XForms 的函数计算表达式)。

如果处理逻辑非常复杂,比如,需要结合父结点来处理子结点的逻辑。建议在清单 3 所示的内存模型中加入一个指向父结点的句柄(线索二叉树)。除此之外,本文所说的方法还可以应用到更多的应用场景,在此不一一叙述。



参考资料



关于作者

李伟,2006 年 4 月毕业于上海交通大学,获计算机应用技术专业硕士学位。 目前在 IBM 中国软件开发中心工作。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款