扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
一.前言
Tuxedo 8.1 集成了Apache Xerces C++ Version 1.7 XML Parser。Xerces 1.7既可以SAX事件驱动方式进行逐行解析,又可以DOM通过对象的树状集合访问XML Buffer。
xmlstockapp是Tuxedo 8.1自带的一个用于说明其集成的Xerces 1.7 XML Parser的功能的一个例子。xmlstockapp实现的功能如下:Client读取一个XML文件,把XML文件的内容置于XML Buffer中,向Server端发送请求;Server端应用进程对接收到的XML Buffer数据进行解析,并在XML Buffer中加入一个新的Element后,将新生成的XML Buffer数据传回Client端;Client端收到Server端返回的XML Buffer数据,对之进行解析并将其打印出来。
这个例子看似功能简单,但其源代码却要将近3000行(不包括相关的.h库文件中的代码)。例子的Readme文件和Tuxedo 8.1 OnlineDoc中只介绍了如何运行这个例子,而要能真正通过这个例子举一反三、灵活运用,那是非要读透它的源代码不可。写这篇文章,只是想从这堆略显纷乱的代码理出一条思路来,仅供参考。
二.Client端程序源代码解析
从xmlstockapp目录下的README.nt文件可知编译Client端程序采用需如下命令行输入:
buildclient -o Client -f Client.cpp -f SAXPrint.cpp -f SAXPrintHandlers.cpp -f %TUXDIR%\lib\libtxml.lib
很容易看出,Client端程序对XML Buffer的解析采用了SAX解析方式。
1. 先看Client.cpp,一段典型的request/response同步调用,唯一显眼的语句便是parseXMLBuffer(rcvbuf);
这句函数调用,其函数声明为:
extern void parseXMLBuffer (char * rcvbuf);
这是一个在SAXPrint.cpp中定义的外部函数,用于解析XML Buffer的数据并将其打印出来。
2. 再看SAXPrint.cpp和SAXPrint.hpp这两个源文件。
主要分析void parseXMLBuffer(char* xmlbuf)这一函数的实现:
a) 初始化:
XMLPlatformUtils::Initialize();
b) 声明一个SAXParser对象:
SAXParser* parser = new SAXParser;
c) 然后用XML Buffer的地址初始化一个MemBufInputSource对象:
MemBufInputSource* memBufIS = new MemBufInputSource( (const XMLByte*)xmlbuf, strlen(xmlbuf)-1, bufId, true);
d) 然后声明一个SAXPrintHandlers对象:
SAXPrintHandlers handler(encodingName, unRepFlags);
SAXPrintHandlers类在SAXPrintHandlers.hpp和SAXPrintHandlers.cpp中声明和定义。
e) 将handler设为parser的DocumentHandler和ErrorHandler:
parser->setDocumentHandler(&handler);
parser->setErrorHandler(&handler);
SAXPrintHandlers类间接继承了DocumentHandler类和ErrorHandler类。
f) 用SAXParser::parse (const InputSource& source, const bool reuseGrammar = false)方法进行解析:
parser->parse(*memBufIS);
g) 删除SAXParser对象:
delete parser;
h) 中止:
XMLPlatformUtils::Terminate();
3. 再看SAXPrintHandlers.cpp和SAXPrintHandlers.hpp这两个源文件。
SAXPrintHandlers类继承了HandlerBase类和XMLFormatTarget类:
a) HandlerBase类主要表示了SAX默认解析行为的接口,它继承了EntityResolver, DTDHandler, DocumentHandler和ErrorHandler这四个类。其中,DocumentHandler,ErrorHandler和DTDHandler这三个类的成员方法主要表示了当SAX解析时各种被触发的相应事件的回调(callback)执行函数。
b) XMLFormatTarget类主要对XML数据进行格式化操作。
重载你所需要实现的成员函数,尤其是那些相应SAX解析时各种被触发的事件的回调(callback)执行函数(这很类似于微软MFC的设计思想)。尤其是对DocumentHandler和ErrorHandler这两个类的一些成员方法的重载:
a) 重载DocumentHandler类的成员方法可助你实现在SAX解析器
开始解析XML文档时 [void startDocument();]、
解析到XML文档末尾时 [void endDocument();]、
开始解析XML的一个元素时 [void startElement(const XMLCh* const name, AttributeList& attributes);]、
解析完XML的一个元素时 [void endElement(const XMLCh* const name);]、
解析XML元素中的字符时[void characters(const XMLCh* const chars, const unsigned int length);]
等等这些事件触发的时候你所需要添加的执行动作,方法是把这些执行动作的代码加入这些回调函数中即可。
b) 重载ErrorHandler类的成员方法可助你实现在SAX解析器在解析过程中,在遇到各种异常时触发的事件而执行的回调函数中加入一些客户化的操作(比如打印出异常信息)。可能的异常类型可包括:
void warning(const SAXParseException& exception);
void error(const SAXParseException& exception);
void fatalError(const SAXParseException& exception);
void resetErrors();
小总结:
调用Xerces 1.7的SAX解析器的关键步骤:
继承HandlerBase类,重载其相应的有关SAX解析时事件响应的回调(callback)执行函数,并把客户化的代码加入其中。
三.Server端程序源代码解析
从xmlstockapp目录下的README.nt文件可知编译Server端程序采用需如下命令行输入:
buildserver -o stockxml -f stockxml.cpp -f DOMTreeErrorReporter.cpp -s STOCKQUOTE -f %TUXDIR%\lib\libtxml.lib
buildserver -v -s STOCKQUOTE -f stockxml_c.c -o stockxml_c -f xmlWrapper.cpp -f DOMTreeErrorReporter.cpp -f %TUXDIR%\lib\libtxml.lib
很容易看出,Server端程序对XML Buffer的解析采用了DOM解析方式。
可以看到,Tuxedo Server端有两个应用进程:stockxml和stockxml_c。这两个应用进程放在不同的GROUP中,从而可利用XML域的值进行Data Dependent Routing。从实现角度来说,这两个应用进程在实现上的区别仅在于封装形式上的不同,故只需分析二者之一的源代码即可,我们就分析stockxml应用进程的源代码。
1. 先看stockxml.cpp源文件中的STOCKQUOTE Service,核心语句便是
parseXMLBuffer(&rqst->data);
这句函数调用,其函数声明为:
void parseXMLBuffer(char** xmlbuf);
此函数用于将输入的XML Buffer数据进行解析,并酌情在XML Buffer数据中加入一些新元素。
2. 再看parseXMLBuffer( )的内部实现。
a) 初始化:
XMLPlatformUtils::Initialize();
b) 用XML Buffer的地址初始化一个MemBufInputSource对象:
MemBufInputSource* memBufIS = new MemBufInputSource( (const XMLByte*)*xmlbuf, strlen(*xmlbuf)-1, bufId, true);
c) 声明一个DOMParser对象:
DOMParser *parser = new DOMParser;
d) 设置DOM解析时的异常处理响应对象:
DOMTreeErrorReporter *errReporter = new DOMTreeErrorReporter();
parser->setErrorHandler(errReporter);
就这一点(异常处理响应)来说,Xerces 1.7的DOM解析几乎完全借鉴了其SAX解析的实现方式。源代码详见DOMTreeErrorReporter.hpp和DOMTreeErrorReporter.cpp。
e) 调用DOMParser对象的parse方法进行DOM解析:
parser->parse(*memBufIS);
执行了这步操作后,memBufIS中的XML数据已经以树型数据结构加载到parser对象中了。
f) 得到DOM文档对象:
DOM_Document document = parser->getDocument();
实质上,DOM_Document类是DOM_Node类的子类,表示装入的整个DOM树的根结点。
g) 获得DOM树的根元素:
DOM_Element topLevel = document.getDocumentElement();
DOM_Element也是DOM_Node类的子类。
h) 然后以document对象和topLevel对象作为输入参数调用以下函数对内存中的DOM树进行遍历,并根据某些条件加入一些新的Element结点:
walkTree(topLevel, document);
i) 接下来的一些代码主要功能是将XML文件中encoding的值由UTF-8换为LATIN1。
j) 删除DOMParser对象:
delete parser;
k) 中止:
XMLPlatformUtils::Terminate();
3. void walkTree( DOM_Node node, DOM_Document document)函数内部实现代码解析:
这是一个非常经典的对树的先序遍历的过程。
a) 首先判断node节点是否为Element节点:
if (node.getNodeType() == DOM_Node::ELEMENT_NODE)
b) 如果不是,则walkTree( )调用结束。
如果是,则判断该节点的名字是否为"stock_quote":
if (node.getNodeName().equals ("stock_quote"))
i) 如果是,则调用以下函数:
walkStockInfo(node, document);
void walkStockInfo( DOM_Node node, DOM_Document document)函数主要操作是在名字为"symbol"、值为"BEAS"的Element节点的子节点集尾部追加一个新节点 :
if ((children.item(i).getNodeName().equals("symbol")) &&
(children.item(i).getFirstChild().getNodeValue().equals("BEAS")))
{
DOM_Element element = document.createElement("price");
element.setAttribute("type","trade");
element.setAttribute("value", "12.218");
node.appendChild(element);
}
ii) 如果不是,则:
先得到node节点的孩子节点集:
DOM_NodeList children = node.getChildNodes();
然后依次对node节点的孩子节点集中的孩子节点进行walkTree( )递归调用:
for (int i=0; ibr> walkTree(children.item(i), document);
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者