在这篇由两部分组成的文章里,将讨论XPath和它在无线应用程序中的使用。 在文章的第一部分,我们将探讨把XPath应用到XML和在无线领域与之配合的WBXML(Wireless
Binary XML,无线二进制XML)。 我们还将讨论设计一个适合于小型无线设备的XPath处理引擎。本文将遵循W3C推荐的XPath
1.0。
XPath可以为我们做什么?
XPath允许我们处理XML,并在一个XML文件中搜索特定的结点。在某种程度上,XPath对于XML就像SQL对于关系数据库,即使W3C
XML查询语言不久将要问世。 本节给出XPath的一个简略的概述。
代码段1是一个WEB Services Definition Language(WEB服务定义语言)文件。 WSDL是一个基于XML的用于描述WEB服务接口的语法。
代码段1中的根元素是definitions。 它有一个name属性,指出它定义的WEB服务的名称。 在我们的例子中,它是BillingService。
在处理一个WSDL文件的时候,我们想提取出WEB服务的名称。 我们可以使用下面的简单的XPath查询来提取WEB服务的名称∶
./child::node()[1]/attribute::name
你可以看到XPath查询被"/"分隔成三部分。 第一部分只是一个点(.)。 在一个查询的起始处的点号意味着你想从XML文件的头开始检索。
第二部分(child::node()[1])的意思是:寻找第一个子元素结点。 第一部分和第二部分的综合效应(./child::node()[1])就是:寻找XML文件的第一个元素。
第三部分(attribute::name)的意思是:寻找一个name属性的值。 整个查询语句的意思就是:寻找XML文件中第一个元素的name属性的值。
回过头来,我们在代码段1 BillingService WSDL文件中应用这个查询。
下面的查询类似于我们在上面讨论的例子,搜索WSDL文件的目标域名空间:
./child::node()[1]/attribute::targetNamespace
现在让我们看一个更加复杂的例子以加深对查询的了解。 看一看下面的XPath查询并设法把它翻译成为日常的话:
./child::node()[1]/child::service/child::port/child::soap:address/attribute::location
用简单的话语表示就是:
寻找XML文件的第一个子节点,然后在第一个子节点里面寻找一个service子元素,那么在service元素内寻找一个port元素,然后寻找port元素内的属于SOAP域名空间的address元素并返回它的location属性的值。
重要的XPath定义
Location step, context node, node-set 和 location path:
一个XPath查询的各部分是顺序地取值。 各个部分都被称为是一个location step。 第一个location step的结果被给与下一个location
step,以此类推,直到你完成整个查询。 每个location step的结果是一个节点的集合,称为一个node-set。 结点集中的每个结点在取值期间被称为context-node。
由此可见,每个location step生产一个node-set,然后转交给下一个location step。 结点集中的每个结点都将作为一个context-node和将要被下一个location
step取值的node-set,以此类推。 一系列location step构成了完整的location path。
axis和node-test:
在一个"/"之后指定一个子节点意味着你想寻找子元素。 而在一个"/"之后指定一个属性意味着你想寻找属性。
子节点和属性关键字被被称作axis。 一个axis指定了你检索的方向。 其它XPath axis包括descendant(意思是你不仅仅对直接子节点元素感兴趣,而且也对它们的子节点,即孙节点感兴趣),parent(直接父节点),ancestor(父节点、祖父节点、曾祖父节点等等)等等。
你还必须注意每个axis后面跟着的双冒号(::),以及后面跟着得我们寻找的结点的名称(在attribute::targetNamespace中为targetNamespace)。
出现在双冒号之后的名称被称为一个node-test(我们需要的结点必须通过的一个试探)。
缩写的语法
我们前面研究过的查询语句./child::node()[1]/attribute::name还可以写成./child::node()[1]/@name。在这里,我们把attribute::name缩写成@name。
@是attribute axis的缩写。
另一个常见的缩写是省略子节点axis。 你不必指定子节点axis,因为XPath把子节点作为缺省的axis。 所以,下面的两个查询会生产一样的结果:
./child::node()[1]/child::service/child::port/child::soap:address/attribute::location
./node()[1]/service/port/soap:address/attribute::location
XPath查询返回什么值?
上述的查询返回属性的值。 XPath还可以返回XML元素。 让我们把下面的XPath查询应用到代码段1中的WSDL文件中去:
./node()[1]/child::message[@name="MonthNumber"]
这个查询在WSDL文件中寻找第一个结点(definitions元素)。 然后它寻找所有的有名为message的definitions元素的子节点。
查询语句还要寻找message元素的的一个附加条件([@name="MonthNumber"])。 这个条件说明message元素应该有一个名为MonthNumber的属性。
代码段1的WSDL文件中有两个message元素。 查询语句将返回name属性是MonthNumber的message元素。
多XPath查询协同工作
如果我们想要使用一个动态值名来寻找一个message元素该怎么办? 例如,在WSDL处理应用程序中,我们经常需要寻找一个name属性匹配input或者output元素的message属性值的message元素。
在代码段1中,我们有一个input元素和两个output元素,每个都有一个message属性。 每一个message属性的值都匹配一个message元素的name属性。
代码段1中的input和output元素都属于两个operation元素。 每个operation元素都有一个name属性。
WSDL处理方案提供给我们operation元素的名称,然后让我们寻找与它的输入/输出元素相关联的message元素。
下面的查询为我们执行这个任务,为了使大家看得更加清楚,我把查询分成了两行:
./node()[1]/child::message[@name=//node()/portType/*[
@name="getBillForMonth"]/child::*/@message]
把这个查询和上一个查询进行比较, 你会发现仅有的差异是" MonthNumber "已经替换为另一个XPath查询:
//node()/portType/*[@name="getBillForMonth"]/child::*/@message
这个例子表明XPath语法允许我们把多个XPath查询组合成为单个复杂的查询。 这在许多实用的存在XML处理任务的应用程序中对我们将会有很大帮助,比如寻找特别的信息,然后依靠读出的信息迅速地转到同一个XML文件上的另一个位置。
无线应用程序中的XPath
XML在无线应用程序中使用和普通的WEB中同样的方法工作。 无线客户端中的WSDL处理需求与台式机客户端中是相同的。
然而,为了解决带宽限制的问题,WAP Forum(http://www.wapforum.org/)提出了XML的一个压缩版本,名为无线二进制XML(Wireless
Binary XML,WBXML),这个版本能够被使用WAP的设备所采用。
我们可以看到,XPath是设计来操作树形的XML结构的。WBXML保持相同的抽象XML结构,即使它的具体的表现形式有差别,所以在把一个XPath查询应用到XML文件或者WBXML上的时候,并没有概念上的差异。
为了说明这一点,让我们研究一个XML文件如何被转换为WBXML。
一个从XML转化到WBXML的例子
让我们研究一下代码段2中的简单的XML文件。 我们要让它非常小,因为只能包含一些基本的WBXML。
一个WBXML文件要以一个WBXML版本说明作为文件的开始。 我们使用WBXML版本1.3,用单字节0x03表示。完整的翻译见代码段3。
接下来的一个字节表示DTD。 我们正在使用一个未知的DTD,所以用0x01表示。
第三个字节表示字符集。 在本文中,我们使用美国ASCII字符集,由0x03表示(见代码段3中WBXML字节流的第三个字节)。
第四个字节是字符串表的长度,字符串表是用来重复使用一个XML文件中多个位置产生的字串。 字符串表可以通过在表中只写一次字符串,然后从不同的位置引用它,用这种方法可以更好的压缩字符串表。
因为在我们的简单的XML文件中没有任何字符出现过两次,所以我们不使用字符串表;我们把它的长度设为0x00(WBXML文件的第四个字节)。
下一个是operation元素。我们使用下面的字节来表示我们的XML元素:
Operation 元素 0x05
Input 元素 0x06
Output 元素 0x07
WBXML假定所有的相关的部分都懂得标记的含义,就像了解相应的XML标记一样。 所以我们可以直接地使用那些标记,假定一个标记在需要的地方可以被翻译为元素名。
因此,我们下一个字节是0x05(operation元素)然后是0x06(input元素)。
接下来是WBXML文件中的"My input"。 0x03标记一个字符串的起始。 一个空字符(0x00)标记字符串的结尾。
注意相同的字节值0x03在前面已经用来标记WBXML版本和字符集声明。 这并不产生任何的不确定性,因为WBXML文件的前三个字节是为XML、DTD和字符集声明所保留的。
实际的WBXML数据有效内容是从第四个字节开始的。
0x01是结束标记。 开始标记(0x05、0x06和0x07)与结束标记(0x01)的结合生产和原始的XML文件相同的嵌套结构。
WBXML文件的余下的部分遵循相同的逻辑。 这个例子说明WBXML在不改变结构的情况下缩小了XML文件的尺寸。
设计一个小型的XPath引擎
我们现在将考虑设计一个小型的XPath引擎用来操作DOM。 DOM实现在主要的无线平台上被使用,比如WinCE和J2ME。
考虑两个伪代码类XPathExpression(目录4)和LocationStep(目录5)。 这两个类共同实现一个XPath引擎。
XPathExpression的构造器使用了两个字符串类型的参数:一个XML文件和一个XPath表达式。 它把XML文件装载入一个DOM对象。
它还标记XPath表达式字符串为更小的字符串组成的数组,每个小字符串表示一个XPath location step。 标记化的时候,它还能把缩写的XPath语法翻译成为正常的全文拼写的表达式。
下一步将执行一个循环,其中的每个location step字符串都将一个接一个的传入LocationPath构造器。 在LocationPath对象创建之后,它的getResult方法被调用。
getResult方法的每个调用都需要一个节点数组(一个结点集)作为参数。 getResult方法将在结点集的每个结点上执行检索然后返回另一个匹配location
path检索需要的节点数组(另一个结点集)。
从每个location step得到的结点集被送到下一个location step,直到所有的XPath查询中的所有的location
step都被遍历完。
现在让我们看看LocationPath类的getResult方法内部发生了什么事。 构造器已经把location step解析为axis和node-test字符串。getResult方法会知道我们需要使用哪个axis,然后调用相应的顺序操作。在例子中,我们仅仅实现子节点和后代axis。父axis和祖先axis也可以按类似方式来实现。
小结
在本文中,我介绍各种各样XPath查询并讨论了如何把它们应用到XML和WBXML格式中去。 我还介绍了一个XPath引擎的基本设计思路。
下次,我们将使这个设计更进一步并包含对更多XPath特性的支持。
代码段1:
<?xml version="1.0" encoding="UTF-8" ?>
<wsd:definitions name="BillingService"
targetNamespace="http://www.ElectricSupplyCompany.com/BillingService-interface"
xmlns:wsd="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="http://www.ElectricSupplyCompany.com/BillingService"
xmlns:xsd="http://www.w3.org/1999/XMLSchema">
<message name="MonthNumber">
<part name="number" type="xsd:int"/>
</message>
<message name="TotalBill">
<part name="bill" type="xsd:int"/>
</message>
<portType name="BillingService_port">
<operation name="getBillForMonth">
<input message="MonthNumber"/>
<output message="TotalBill"/>
</operation>
<operation name="getCurrentBill">
<output message="TotalBill"/>
</operation>
</portType>
<binding name="BillingService_Binding" type="BillingService_port">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http" />
<operation name="getBillForMonth">
<soap:operation soapAction="urn:BillingService" />
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:BillingService"
use="encoded" />
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:BillingService"
use="encoded" />
</output>
</operation>
<operation name="getCurrentBill">
<soap:operation soapAction="urn:BillingService" />
<input>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:BillingService"
use="encoded" />
</input>
<output>
<soap:body
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="urn:BillingService"
use="encoded" />
</output>
</operation>
</binding>
<service name="BillingService">
<documentation> Billing Service of Electric Supply Company. </documentation>
<port binding="BillingService_Binding" name="BillingService_ServicePort">
<soap:address location="http://localhost:8080/soap/servlet/rpcrouter"
/>
</port>
</service>
</wsd:definitions>
代码段2:
<?xml version="1.0" encoding="UTF-8" ?>
<operation>
<input>My Input</input>
<output>My Output</output>
</operation>
代码段3:
字节号 字节值 用途
1. 0x03 WBXML版本
2. 0x01 DTD
3. 0x03 字符集
4. 0x00 字符串表长度
5. 0x05 Operation元素的起始
6. 0x06 Input元素的起始
7. 0x03 字符串的起始
8. 'M'
9. 'y'
10. 'I'
11. 'n'
12. 'p'
13. 'u'
14. 't'
15. 0x00 字符串的结束
16. 0x01 Input元素的结束
17. 0x07 Output元素的起始
18. 0x03 字符串的起始
19. 'M'
20. 'y'
21. 'O'
22. 'u'
23. 't'
24. 'p'
25. 'u'
26. 't'
27. 0x00 字符串的结束
28. 0x01 Output元素的结束
29. 0x01 Operation元素的结束
代码段4:
public class XPathExpression
{
DOM_Document XML_DOM;
Array of Strings XPathLocationStepStringsArray;
Array of XPathLocationSteps XPathLocationStepsArray;
Array of Nodes ResultNodeSet;
//构造器
XPathExpression (string XMLFile, string XPathExpression)
{
XML_DOM = XMLFile loaded into a DOM;
XPathLocationStepStringsArray =Tokenize XPath expression into
smaller strings,where each string is a location step.Also unabbreviate
any XPath abbreviations found;
Integer locationStepCount = Number of location step strings in
XPathLocationStepStringsArray;
String IndividualXPathLocationStepString;
XPathLocationStep IndividualXPathLocationStep;
ResultNodeSet = XML_DOM;
Repeat locationStepCount times from i = 0 to locationStepCount-1:
{
IndividualXPathLocationStepString = XPathLocationStepStringsArray[i];
IndividualXPathLocationStep = a new object of XPathLocationStep(
IndividualXPathLocationStepString);
ResultNodeSet = IndividualXPathLocationStep.getResult(ResultNodeSet);
}
}//构造器
public Array of nodes getResult()
{
return ResultNodeSet;
}
}
代码段5:
public class XPathLocationStep
{
String Axis;
String NodeTest;
Array of Nodes OutputNodeSet;
//构造器
XPathLocationStep(String XPathLocationStepString)
{
Resolve the XPathLocationStepString into Axis and NodeTest;
}//构造器
Array of Nodes getResult(Array of Nodes ContextNodeSet)
{
OutputNodeSet = new Array of Nodes;
Integer NodeCount =Number of nodes in ContextNodeSet;
if (Axis is equal to "child" or "descendant")
{
Repeat NodeCount times for i = 0 to NodeCount-1
{
Node node = ContextNodeSet[i];
Integer ChildCount = Number of node's children;
Repeat ChildCount times for j = 0 to ChildCount-1
{
Node ChildNode =node.getChildElement(j);
String ChldName =Name of the ChildNode;
if (NodeTest is equal to ChildName)
Add ChildNode to OutputNodeSet;
if (Axis is equal to "descendant")
{
Array of Nodes Descendants =getMatchingDescendants(ChildNode);
Add MatchingDescendants to OutputNodeSet;
}
}
}
}
}
private Array of Nodes getMatchingDescendants(Node node)
{
Array of Nodes MatchingDescendants;
Integer ChildCount = Number of node's children;
Repeat NodeCount times for j = 0 to ChildCount-1
{
Node ChildNode =node.getChildElement(j);
String ChildName =Name of the ChildNode;
if (NodeTest is equal to ChildName)
Add ChildNode to MatchingDescendants;
Array of Nodes MoreDescendants =getMatchingDescendants(ChildNode);
Add MoreDescendants to MatchingDescendnts;
}
return MatchingDescendants;
}
}
|