我这里有代码,要吗 方法一: unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs,MSXML2_TLB, xmldom, XMLIntf, ExtCtrls, DBCtrls, StdCtrls, Grids, DBGrids, DB, msxmldom, XMLDoc, DBTables, DBClient,ComObj, ComCtrls; type TForm1 = class(TForm) Button1: TButton; Table1: TTable; XMLDocument1: TXMLDocument; DataSource1: TDataSource; Label1: TLabel; Button3: TButton; DBNavigator2: TDBNavigator; PageControl1: TPageControl; TabSheet1: TTabSheet; TabSheet2: TTabSheet; Memo1: TMemo; DBGrid1: TDBGrid; DateTimePicker1: TDateTimePicker; ComboBox1: TComboBox; Label2: TLabel; procedure Button3Click(Sender: TObject); procedure Button1Click(Sender: TObject); procedure ComboBox1Change(Sender: TObject); private { Private declarations } doc :IXMLDomDocument; root,child,child1 :IXMLDOMElement; function makeXml(table:TTable):Integer; public { Public declarations } end;var Form1: TForm1;implementation{$R *.dfm}procedure TForm1.Button3Click(Sender: TObject); begin Form1.Close end;//添加makeXml函数单元。它将通过读取DBDEMOS中contry表中的数据生成一个XML文件。 function TForm1.makeXml(table:TTable):Integer; var i : Integer; xml,temp : String; begin try table.close; table.open; xml := table.TableName; doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument; //Set the root name of the xml file as that of the table name. //In this case "country" root := doc.createElement(xml); doc.appendchild(root); //This while loop will go through the entaire table to generate the xml file while not table.eof do begin //adds the first level children , Records child:= doc.createElement('Records'); root.appendchild(child); for i:=0 to table.FieldCount-1 do begin //adds second level children child1:=doc.createElement(table.Fields[i].FieldName); child.appendchild(child1); //Check field types case TFieldType(Ord(table.Fields[i].DataType)) of ftString: begin if Table.Fields[i].AsString ='' then temp :='null' //Put a default string else temp := table.Fields[i].AsString; end;
ftInteger, ftWord, ftSmallint: begin if Table.Fields[i].AsInteger > 0 then temp := IntToStr(table.Fields[i].AsInteger) else temp := '0'; end; ftFloat, ftCurrency, ftBCD: begin if table.Fields[i].AsFloat > 0 then temp := FloatToStr(table.Fields[i].AsFloat) else temp := '0'; end; ftBoolean: begin if table.Fields[i].Value then temp:= 'True' else temp:= 'False'; end; ftDate: begin if (not table.Fields[i].IsNull) or (Length(Trim(table.Fields[i].AsString)) > 0) then temp := FormatDateTime('MM/DD/YYYY', table.Fields[i].AsDateTime) else temp:= '01/01/2000'; //put a valid default date end; ftDateTime: begin if (not table.Fields[i].IsNull) or (Length(Trim(table.Fields[i].AsString)) > 0) then temp := FormatDateTime('MM/DD/YYYY hh:nn:ss', Table.Fields[i].AsDateTime) else temp := '01/01/2000 00:00:00'; //Put a valid default date and time end; ftTime: begin if (not table.Fields[i].IsNull) or (Length(Trim(table.Fields[i].AsString)) > 0) then temp := FormatDateTime('hh:nn:ss', table.Fields[i].AsDateTime) else temp := '00:00:00'; //Put a valid default time end; end; // child1.appendChild(doc.createTextNode(temp)); end; table.Next; end; doc.save(xml+'.xml'); memo1.lines.Append(doc.xml); Result:=1; except on e:Exception do Result:=-1; end; end; //在Button1的onclick事件中调用上面的函数 procedure TForm1.Button1Click(Sender: TObject); begin showmessage('开始生成!'); if makeXml(table1)=1 then showmessage('XML Generated') else showmessage('Error while generating XML File'); end; procedure TForm1.ComboBox1Change(Sender: TObject); begin showmessage('改变表名'); end;end.
方法二: 解析XML XML可以以一种通用的方式来储存数据,数据可以使用DOM解析器解析。本文就将讨论如何使用微软提供的XML解析器来解析简单的XML文件。 DOM是文档对象模型的缩写(Document Object Model)。我们可以使用DOM来查找HTML或XML文档中的元素。本文将不会详细解释DOM和XML,想了解这方面更多的信息请查阅其他资料。 本文中将要用到的微软的XML解析器是随Internet Explorer缺省安装的,因此在绝大多数的Windows中都可以获得支持。建立一个简单的XML解析程序 同其他的COM应用程序一样,要想使用XML解析器,我们需要引入XML控件的类型库。注意这里要先确保系统中安装了Internet Explorer 5.0。选择菜单命令Project | Import Type Library ,选择引入Microsoft XML, version 2.0(如图1.61)。点OK按钮,就会在ActiveX控件面板上安装XML解析控件。 然后创建一个新的应用程序,在窗体上放置Tbutton、TMemo和TListBox控件。再放上一个刚才安装的XML解析控件TDomDocument。为按钮创建一个OnClick事件,输入下面代码: procedure TForm1.Button2Click(Sender: TObject); var Len,i: Integer; ElemList: IXMLDOMNodeList; begin DOMDocument1.load('c:\temp\sam1.xml'); Len := DOMDocument1.documentElement.childNodes.Get_length; for i := 0 to Len - 1 do begin Memo1.Lines.Add('节点名称 := ' + OMDocument1.documentElement.Get_ nodeName); Memo1.Lines.Add(''); Memo1.Lines.Add(DOMDocument1.documentElement.childNodes.item[i].Text); end; ElemList := DOMDocument1.documentElement.getElementsByTagName('CD'); for i := 0 to ElemList.length -1 do ListBox1.ITems.Add(ElemList.item[i].xml); end; 上面的代码使用XML解析器来遍历一个XML文件,然后显示它的内容。首先,在第一行代码中,我们使用DOMDocument控件加载了一个XML文件,下面就是XML文件的内容: <HTML> <HEAD> <TITLE>Sample XML File</TITLE> </HEAD> <BODY> <P>Right under this line I insert an XML data island.</P> <XML ID="CDXML"> <CDS> <CD>Two Against Nature</CD> <CD>Giant Steps</CD> <CD>Round About Midnight</CD> <CD>Imaginary Day</CD> </CDS> </XML> </BODY> </HTML> 代码的第二部分是查找在文档中最顶层有多少个节点。文档的节点是按树状结构组织的,比如<CD>节点就在<CDS>下面一级。下面这条语句就是为获得节点数的: Len := DOMDocument1.documentElement.childNodes.Get_length; 接下来就是遍历这些节点然后察看它们的内容,代码如下: var Len,i: Integer; ElemList: IXMLDOMNodeList; Begin //省略… DOMDocument1.load('c:\temp\sam1.xml'); Len := DOMDocument1.documentElement.childNodes.Get_length; for i := 0 to Len - 1 do begin Memo1.Lines.Add('节点名称 := ' + OMDocument1.documentElement.Get_ nodeName); Memo1.Lines.Add(''); Memo1.Lines.Add(DOMDocument1.documentElement.childNodes.item[i].Text); end; //省略… end; 代码忽略了顶层的HTML标记,然后把XML内容分成两部分,第一个节点包括HEAD元素,第二个节点包括 BODY元素。另外要想更深入挖掘文档内容,可以使用ChildNodes属性来察看子节点的内容,这在后面将会谈到,这里我们还想知道特殊节点的信息,比如想知道所有关于CD的销售信息。要想获得这方面的信息,可以使用GetElementsByTagName 方法,代码如下: var Len,i: Integer; ElemList: IXMLDOMNodeList; begin //省略… ElemList := DOMDocument1.documentElement.getElementsByTagName('CD'); for i := 0 to ElemList.length -1 do ListBox1.ITems.Add(ElemList.item[i].xml); end; 正如我们看到的,可以直接获得文件中特定的标记,这里简单地输出了所有<CD>标记列表: <CD>Two Against Nature</CD> <CD>Giant Steps</CD> <CD>Round About Midnight</CD> <CD>Imaginary Day</CD> 从上面我们可以看到解析XML是非常简单的,整个程序运行结果如图1.62所示。
深入XML解析 下面演示如何解析更复杂的XML文件,并用TTreeView控件来显示XML文档的树状结构。要想知道XML的标准定义,可参见http://www.w3.org/XML/。 下面的例子中将使用一个封装了XML解析器的对象,它可以简化对XML文件的解析。在具体解释这个对象前,先来看看我们将要实现的例子,这个例子允许选择想要解析的XML文件,然后查找主要的节点,并显示在页面右侧的树视图中(如图1.63),另外这里用来演示XML解析的文件是一个MIDAS生成的XML数据包文件。 如果打开文件的METADATA节点,将会看到数据字段和参数节点,如图1.64所示。 由图可见字段名为KeyNum、KeyFields等,而其中的数据分别为01-0001-W1-B、1、BLWORD 等。同时还可以知道Borland是如何把数据保存在XML文件的ROW元素中的。下面是Borland在XML文件中保存原始数据的方式: <ROW KeyNum="02-0004-W1-B"KeyFields="14"FileName="BLWORD"Chap-ter="2"Num="4"/> 他们没有用下面这种方式保存: <ROW> <KEYNUM>02-0004-W1-B</KEYNUM> <KEYFIELDS>14</KEYFIELDS> <FILENAME>BLWORD</FILENAME> <CHAPTER>Chapter</CHAPTER> <NUM>4</NUM> </ROW> 可能的原因是为了节省空间吧,后面我们还将演示如何解析第二种结构的XML文档。要注意的是对于 Borland MIDAS技术来说,它是把每个数据记录的内容都按Row元素的属性进行保存,从树视图中看好像KeyNum、KeyFields、FileName、Chapter 和Num字段是Row元素的元素节点,这其实是一种误解。缺省条件下,封装的对象不会把属性显示为如图1.65的独立的子节点形式,而是显示为如图1.66的形式。理解代码 下面该是讨论前面提到的封装对象问题的时候了,这个对象名为TCSCXMLParserMS。它的核心方法是WriteIt。这种方法使用微软的解析引擎来解析XML文件,每当它查找到文件中的节点后,它将会激发事件,并在事件中返回节点名和节点在文档节点树中的深度。 使用TCSCXMLParseMS对象时,传递一个IXMLDomNode接口给WriteIt方法,WriteIt方法然后就会查找所有的子节点,每发现一个节点就会激发相应的事件,代码如下: if OpenDialog1.Execute then begin Memo1.Clear; TreeView1.Items.Clear; DomDocument1.Load(OpenDialog1.FileName); AddToTree(Self, 0, 'Doc'); Parse := TCSCXMLParseMS.Create; // 切环 BreakoutAttributes属性 Parse.BreakoutAttributes := True; Parse.OnElementNode := AddToTree; Parse.OnAnyNode := AddToMemo; Parse.WriteIt(DomDocument1.DocumentElement, 0); parse.Free; end; WriteIt 方法的基本结构非常简单,它处理每一种可能的节点类型: procedure TCSCXMLParseMS.WriteIt(toWrite: IXMLDomNode; Level: Integer); begin case (toWrite.NodeType) of NODE_TEXT: NODE_PROCESSING_INSTRUCTION: NODE_DOCUMENT: NODE_ELEMENT: NODE_ENTITY_REFERENCE: NODE_CDATA_SECTION: NODE_COMMENT: else end; end; 上面的节点类型定义常数声明在MSXML_TLB.pas文件中,很多节点类型包含子节点,下面的代码演示了如何递归子节点: NODE_ENTITY_REFERENCE: begin // 省略… child := toWrite.FirstChild; while child <> nil do begin WriteIt(child, Level); child := child.NextSibling; end; end; 其他节点类型的处理同NODE_ENTITY_REFERENCE类型非常类似。 接下来要讨论的则是对象的事件机制。理解事件模型 TCSCXMLParseMS 对象有两个事件,一个返回描述节点的字符串,另一个返回节点元素,节点属性和文本节点的相关信息。后一个事件还会返回一个整数表明节点在节点树中的深度。下面是两个事件的定义: TAnyNodeEvent = procedure(Sender: TObject; Value: string) of object; TElementNodeEvent = procedure(Sender: TObject; Level: Integer; Value: string) of object; 对于前面运行的示意图来说,TAnyNodeEvents 事件返回的信息显示在窗体的左侧,而TElementNodeEvents 事件返回的信息显示在右侧的TTreeView 控件中。 下面是TCSCXMLParser对象的声明,它显示了事件是如何集成到对象中的: TCSCXMLParseMS = class private FBreakoutAttributes: Boolean; FElementNodeEvent: TElementNodeEvent; FAnyNodeEvent: TAnyNodeEvent; function OutputContent(const toWrite: String; doEscapes: Boolean): string; procedure WriteIt(toWrite: IXMLDOMNode; Level: Integer); function GetElementNodeEvent: TElementNodeEvent; procedure SetElementNodeEvent(const Value: TElementNodeEvent); procedure SetAnyNodeEvent(const Value: TAnyNodeEvent); public procedure WriteDom(toWrite: IXMLDOMNode); property OnAnyNode: TAnyNodeEvent read FAnyNodeEvent write SetAnyNodeEvent; property BreakoutAttributes: boolean read FBreakoutAttributes write FBreakoutAttributes; property OnElementNode: TElementNodeEvent read GetElementNodeEvent write SetElementNodeEvent; end; 对象包含类型为TElementNodeEvent和TAnyNodeEvent的属性OnAnyNode和OnElementNode,下面是WriteIt 方法中调用事件的代码: if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level, DomNode.NodeValue); 代码首先使用Assign 方法判断FElementNodeEvent 事件指针是否为nil。如果是的话,表明没有任何方法被赋值给指针,因此不会激发事件,否则事件的处理方法就会被调用,并返回相关的数据。 查找文本节点部分的代码可以很好地用来理解事件机制的工作方式: NODE_TEXT: begin if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, 'text: ' + toWrite.nodeValue); if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, toWrite.NodeValue); end; 上面的部分代码相对简单,主要是因为文本节点没有子节点,无需递归调用,只要返回相关的文本就可以了。 处理元素 下面该研究WriteIt 方法中处理元素节点和嵌入在其中的属性的部分 ,代码示意如下: ntELEMENT_NODE : begin // 输出元素起始标记 S := '<' + nodeName; // 输出元素的属性 attributes := toWrite.Attributes; attrCount := attributes.Length; if FBreakoutAttributes then if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, NodeName); for i := 0 to attrCount - 1 do begin Attribute := attributes.item(i); S := S + ' ' + attribute.NodeName; S := S + attribute.NodeValue; if FBreakoutAttributes then begin if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue + 1, attribute.NodeName); if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, (Level - AValue) + 2, attribute.NodeValue); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, attribute.NodeName + 'Level: ' + IntToStr(Level)); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, attribute.NodeValue + 'Level: ' + IntToStr(Level)); end; end; // 检查是否有子节点 child := toWrite.FirstChild; if (child <> nil) then begin // 有子节点,加上结束标记,输出子节点 S := S + '>'; if not FBreakoutAttributes then begin if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, S); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, S + 'Level: ' + IntToStr(Level)); end; // 递归调用 while( child <> nil) do begin WriteIt(child, Level); child := child.NextSibling; end;
方法一:
unit Unit1;interfaceuses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,MSXML2_TLB, xmldom, XMLIntf, ExtCtrls, DBCtrls, StdCtrls, Grids,
DBGrids, DB, msxmldom, XMLDoc, DBTables, DBClient,ComObj, ComCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Table1: TTable;
XMLDocument1: TXMLDocument;
DataSource1: TDataSource;
Label1: TLabel;
Button3: TButton;
DBNavigator2: TDBNavigator;
PageControl1: TPageControl;
TabSheet1: TTabSheet;
TabSheet2: TTabSheet;
Memo1: TMemo;
DBGrid1: TDBGrid;
DateTimePicker1: TDateTimePicker;
ComboBox1: TComboBox;
Label2: TLabel;
procedure Button3Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
private
{ Private declarations }
doc :IXMLDomDocument;
root,child,child1 :IXMLDOMElement;
function makeXml(table:TTable):Integer;
public
{ Public declarations }
end;var
Form1: TForm1;implementation{$R *.dfm}procedure TForm1.Button3Click(Sender: TObject);
begin
Form1.Close
end;//添加makeXml函数单元。它将通过读取DBDEMOS中contry表中的数据生成一个XML文件。
function TForm1.makeXml(table:TTable):Integer;
var
i : Integer;
xml,temp : String;
begin
try
table.close;
table.open;
xml := table.TableName;
doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument;
//Set the root name of the xml file as that of the table name.
//In this case "country"
root := doc.createElement(xml);
doc.appendchild(root);
//This while loop will go through the entaire table to generate the xml file
while not table.eof do
begin
//adds the first level children , Records
child:= doc.createElement('Records');
root.appendchild(child);
for i:=0 to table.FieldCount-1 do
begin
//adds second level children
child1:=doc.createElement(table.Fields[i].FieldName);
child.appendchild(child1);
//Check field types
case TFieldType(Ord(table.Fields[i].DataType)) of
ftString:
begin
if Table.Fields[i].AsString ='' then
temp :='null' //Put a default string
else
temp := table.Fields[i].AsString;
end;
ftInteger, ftWord, ftSmallint:
begin
if Table.Fields[i].AsInteger > 0 then
temp := IntToStr(table.Fields[i].AsInteger)
else
temp := '0';
end;
ftFloat, ftCurrency, ftBCD:
begin
if table.Fields[i].AsFloat > 0 then
temp := FloatToStr(table.Fields[i].AsFloat)
else
temp := '0';
end;
ftBoolean:
begin
if table.Fields[i].Value then
temp:= 'True'
else
temp:= 'False';
end;
ftDate:
begin
if (not table.Fields[i].IsNull) or
(Length(Trim(table.Fields[i].AsString)) > 0) then
temp := FormatDateTime('MM/DD/YYYY',
table.Fields[i].AsDateTime)
else
temp:= '01/01/2000'; //put a valid default date
end;
ftDateTime:
begin
if (not table.Fields[i].IsNull) or
(Length(Trim(table.Fields[i].AsString)) > 0) then
temp := FormatDateTime('MM/DD/YYYY hh:nn:ss',
Table.Fields[i].AsDateTime)
else
temp := '01/01/2000 00:00:00'; //Put a valid default date and time
end;
ftTime:
begin
if (not table.Fields[i].IsNull) or
(Length(Trim(table.Fields[i].AsString)) > 0) then
temp := FormatDateTime('hh:nn:ss',
table.Fields[i].AsDateTime)
else
temp := '00:00:00'; //Put a valid default time
end;
end;
//
child1.appendChild(doc.createTextNode(temp));
end;
table.Next;
end;
doc.save(xml+'.xml');
memo1.lines.Append(doc.xml);
Result:=1;
except
on e:Exception do
Result:=-1;
end;
end; //在Button1的onclick事件中调用上面的函数
procedure TForm1.Button1Click(Sender: TObject);
begin
showmessage('开始生成!');
if makeXml(table1)=1 then
showmessage('XML Generated')
else
showmessage('Error while generating XML File');
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
begin
showmessage('改变表名');
end;end.
方法二:
解析XML
XML可以以一种通用的方式来储存数据,数据可以使用DOM解析器解析。本文就将讨论如何使用微软提供的XML解析器来解析简单的XML文件。 DOM是文档对象模型的缩写(Document Object Model)。我们可以使用DOM来查找HTML或XML文档中的元素。本文将不会详细解释DOM和XML,想了解这方面更多的信息请查阅其他资料。 本文中将要用到的微软的XML解析器是随Internet Explorer缺省安装的,因此在绝大多数的Windows中都可以获得支持。建立一个简单的XML解析程序 同其他的COM应用程序一样,要想使用XML解析器,我们需要引入XML控件的类型库。注意这里要先确保系统中安装了Internet Explorer 5.0。选择菜单命令Project | Import Type Library ,选择引入Microsoft XML, version 2.0(如图1.61)。点OK按钮,就会在ActiveX控件面板上安装XML解析控件。 然后创建一个新的应用程序,在窗体上放置Tbutton、TMemo和TListBox控件。再放上一个刚才安装的XML解析控件TDomDocument。为按钮创建一个OnClick事件,输入下面代码: procedure TForm1.Button2Click(Sender: TObject); var Len,i: Integer; ElemList: IXMLDOMNodeList; begin DOMDocument1.load('c:\temp\sam1.xml'); Len := DOMDocument1.documentElement.childNodes.Get_length; for i := 0 to Len - 1 do begin Memo1.Lines.Add('节点名称 := ' + OMDocument1.documentElement.Get_ nodeName); Memo1.Lines.Add(''); Memo1.Lines.Add(DOMDocument1.documentElement.childNodes.item[i].Text); end; ElemList := DOMDocument1.documentElement.getElementsByTagName('CD'); for i := 0 to ElemList.length -1 do ListBox1.ITems.Add(ElemList.item[i].xml); end; 上面的代码使用XML解析器来遍历一个XML文件,然后显示它的内容。首先,在第一行代码中,我们使用DOMDocument控件加载了一个XML文件,下面就是XML文件的内容: <HTML> <HEAD> <TITLE>Sample XML File</TITLE> </HEAD> <BODY> <P>Right under this line I insert an XML data island.</P> <XML ID="CDXML"> <CDS> <CD>Two Against Nature</CD> <CD>Giant Steps</CD> <CD>Round About Midnight</CD> <CD>Imaginary Day</CD> </CDS> </XML> </BODY> </HTML> 代码的第二部分是查找在文档中最顶层有多少个节点。文档的节点是按树状结构组织的,比如<CD>节点就在<CDS>下面一级。下面这条语句就是为获得节点数的: Len := DOMDocument1.documentElement.childNodes.Get_length; 接下来就是遍历这些节点然后察看它们的内容,代码如下: var Len,i: Integer; ElemList: IXMLDOMNodeList; Begin //省略… DOMDocument1.load('c:\temp\sam1.xml'); Len := DOMDocument1.documentElement.childNodes.Get_length; for i := 0 to Len - 1 do begin Memo1.Lines.Add('节点名称 := ' + OMDocument1.documentElement.Get_ nodeName); Memo1.Lines.Add(''); Memo1.Lines.Add(DOMDocument1.documentElement.childNodes.item[i].Text); end; //省略… end; 代码忽略了顶层的HTML标记,然后把XML内容分成两部分,第一个节点包括HEAD元素,第二个节点包括 BODY元素。另外要想更深入挖掘文档内容,可以使用ChildNodes属性来察看子节点的内容,这在后面将会谈到,这里我们还想知道特殊节点的信息,比如想知道所有关于CD的销售信息。要想获得这方面的信息,可以使用GetElementsByTagName 方法,代码如下: var Len,i: Integer; ElemList: IXMLDOMNodeList; begin //省略… ElemList := DOMDocument1.documentElement.getElementsByTagName('CD'); for i := 0 to ElemList.length -1 do ListBox1.ITems.Add(ElemList.item[i].xml); end; 正如我们看到的,可以直接获得文件中特定的标记,这里简单地输出了所有<CD>标记列表: <CD>Two Against Nature</CD> <CD>Giant Steps</CD> <CD>Round About Midnight</CD> <CD>Imaginary Day</CD> 从上面我们可以看到解析XML是非常简单的,整个程序运行结果如图1.62所示。
深入XML解析 下面演示如何解析更复杂的XML文件,并用TTreeView控件来显示XML文档的树状结构。要想知道XML的标准定义,可参见http://www.w3.org/XML/。 下面的例子中将使用一个封装了XML解析器的对象,它可以简化对XML文件的解析。在具体解释这个对象前,先来看看我们将要实现的例子,这个例子允许选择想要解析的XML文件,然后查找主要的节点,并显示在页面右侧的树视图中(如图1.63),另外这里用来演示XML解析的文件是一个MIDAS生成的XML数据包文件。 如果打开文件的METADATA节点,将会看到数据字段和参数节点,如图1.64所示。
由图可见字段名为KeyNum、KeyFields等,而其中的数据分别为01-0001-W1-B、1、BLWORD 等。同时还可以知道Borland是如何把数据保存在XML文件的ROW元素中的。下面是Borland在XML文件中保存原始数据的方式: <ROW KeyNum="02-0004-W1-B"KeyFields="14"FileName="BLWORD"Chap-ter="2"Num="4"/> 他们没有用下面这种方式保存: <ROW> <KEYNUM>02-0004-W1-B</KEYNUM> <KEYFIELDS>14</KEYFIELDS> <FILENAME>BLWORD</FILENAME> <CHAPTER>Chapter</CHAPTER> <NUM>4</NUM> </ROW> 可能的原因是为了节省空间吧,后面我们还将演示如何解析第二种结构的XML文档。要注意的是对于 Borland MIDAS技术来说,它是把每个数据记录的内容都按Row元素的属性进行保存,从树视图中看好像KeyNum、KeyFields、FileName、Chapter 和Num字段是Row元素的元素节点,这其实是一种误解。缺省条件下,封装的对象不会把属性显示为如图1.65的独立的子节点形式,而是显示为如图1.66的形式。理解代码 下面该是讨论前面提到的封装对象问题的时候了,这个对象名为TCSCXMLParserMS。它的核心方法是WriteIt。这种方法使用微软的解析引擎来解析XML文件,每当它查找到文件中的节点后,它将会激发事件,并在事件中返回节点名和节点在文档节点树中的深度。
使用TCSCXMLParseMS对象时,传递一个IXMLDomNode接口给WriteIt方法,WriteIt方法然后就会查找所有的子节点,每发现一个节点就会激发相应的事件,代码如下: if OpenDialog1.Execute then begin Memo1.Clear; TreeView1.Items.Clear; DomDocument1.Load(OpenDialog1.FileName); AddToTree(Self, 0, 'Doc'); Parse := TCSCXMLParseMS.Create; // 切环 BreakoutAttributes属性 Parse.BreakoutAttributes := True; Parse.OnElementNode := AddToTree; Parse.OnAnyNode := AddToMemo; Parse.WriteIt(DomDocument1.DocumentElement, 0); parse.Free; end; WriteIt 方法的基本结构非常简单,它处理每一种可能的节点类型: procedure TCSCXMLParseMS.WriteIt(toWrite: IXMLDomNode; Level: Integer); begin case (toWrite.NodeType) of NODE_TEXT: NODE_PROCESSING_INSTRUCTION: NODE_DOCUMENT: NODE_ELEMENT: NODE_ENTITY_REFERENCE: NODE_CDATA_SECTION: NODE_COMMENT: else end; end; 上面的节点类型定义常数声明在MSXML_TLB.pas文件中,很多节点类型包含子节点,下面的代码演示了如何递归子节点: NODE_ENTITY_REFERENCE: begin // 省略… child := toWrite.FirstChild; while child <> nil do begin WriteIt(child, Level); child := child.NextSibling; end; end; 其他节点类型的处理同NODE_ENTITY_REFERENCE类型非常类似。 接下来要讨论的则是对象的事件机制。理解事件模型 TCSCXMLParseMS 对象有两个事件,一个返回描述节点的字符串,另一个返回节点元素,节点属性和文本节点的相关信息。后一个事件还会返回一个整数表明节点在节点树中的深度。下面是两个事件的定义: TAnyNodeEvent = procedure(Sender: TObject; Value: string) of object; TElementNodeEvent = procedure(Sender: TObject; Level: Integer; Value: string) of object; 对于前面运行的示意图来说,TAnyNodeEvents 事件返回的信息显示在窗体的左侧,而TElementNodeEvents 事件返回的信息显示在右侧的TTreeView 控件中。 下面是TCSCXMLParser对象的声明,它显示了事件是如何集成到对象中的: TCSCXMLParseMS = class private FBreakoutAttributes: Boolean; FElementNodeEvent: TElementNodeEvent; FAnyNodeEvent: TAnyNodeEvent; function OutputContent(const toWrite: String; doEscapes: Boolean): string; procedure WriteIt(toWrite: IXMLDOMNode; Level: Integer); function GetElementNodeEvent: TElementNodeEvent; procedure SetElementNodeEvent(const Value: TElementNodeEvent); procedure SetAnyNodeEvent(const Value: TAnyNodeEvent); public procedure WriteDom(toWrite: IXMLDOMNode); property OnAnyNode: TAnyNodeEvent read FAnyNodeEvent write SetAnyNodeEvent; property BreakoutAttributes: boolean read FBreakoutAttributes write FBreakoutAttributes; property OnElementNode: TElementNodeEvent read GetElementNodeEvent write SetElementNodeEvent; end; 对象包含类型为TElementNodeEvent和TAnyNodeEvent的属性OnAnyNode和OnElementNode,下面是WriteIt 方法中调用事件的代码: if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level, DomNode.NodeValue); 代码首先使用Assign 方法判断FElementNodeEvent 事件指针是否为nil。如果是的话,表明没有任何方法被赋值给指针,因此不会激发事件,否则事件的处理方法就会被调用,并返回相关的数据。 查找文本节点部分的代码可以很好地用来理解事件机制的工作方式: NODE_TEXT: begin if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, 'text: ' + toWrite.nodeValue); if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, toWrite.NodeValue); end; 上面的部分代码相对简单,主要是因为文本节点没有子节点,无需递归调用,只要返回相关的文本就可以了。 处理元素 下面该研究WriteIt 方法中处理元素节点和嵌入在其中的属性的部分 ,代码示意如下: ntELEMENT_NODE : begin // 输出元素起始标记 S := '<' + nodeName; // 输出元素的属性 attributes := toWrite.Attributes; attrCount := attributes.Length; if FBreakoutAttributes then if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, NodeName); for i := 0 to attrCount - 1 do begin Attribute := attributes.item(i); S := S + ' ' + attribute.NodeName; S := S + attribute.NodeValue; if FBreakoutAttributes then begin if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue + 1, attribute.NodeName); if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, (Level - AValue) + 2, attribute.NodeValue); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, attribute.NodeName + 'Level: ' + IntToStr(Level)); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, attribute.NodeValue + 'Level: ' + IntToStr(Level)); end; end; // 检查是否有子节点 child := toWrite.FirstChild; if (child <> nil) then begin // 有子节点,加上结束标记,输出子节点 S := S + '>'; if not FBreakoutAttributes then begin if Assigned(FElementNodeEvent) then FElementNodeEvent(Self, Level - AValue, S); if Assigned(FAnyNodeEvent) then FAnyNodeEvent(Self, S + 'Level: ' + IntToStr(Level)); end; // 递归调用 while( child <> nil) do begin WriteIt(child, Level); child := child.NextSibling; end;