我这有一些资料你看看吧
XML 与数据库表的转换
原著: Sreekumar S
公司: Icode s/w Pvt. Ltd.,Bangalore 参考资料: N/A
问题提出/摘要:
本文将解释如何通过从Paradox表中读取数据生成XML文件,并且将相同的数据插回源表中。不过它还不能工作在memo字段上。我使用MS XML解析器来解析XML文档。因此你的系统须是IE 5.0或以上版本。
回答:
生成XML文件。
我使用下面的转换方法:
I . XML文件的根名与表名相同(本例就是country)。
II. 每条来自于表的记录由<record></record>标记区分。
III. 每个来自于表的数据由其字段名标记加以区分。
- <country>
- <Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
.
.
.
</country>
建立一个新的应用程序。放置一个Button和Table构件于主窗体上。设置表属性如下:
DatabaseName : DBDEMOS
Name : Table1
TableName : country (Remove the extention ".db")
Active : True
选择 Project/Import Type library。将会弹出 "Import Type Library" 对话框。从列表中选择 "Microsoft XML,Version
2.0(version 2.0)" 然后点击 "Create Unit" 按钮。将会有一个 MSXML_TLB 单元加入你的工程.请将 MSXML_TLB 加入你要引用的单元的接口部分。然后在变量部分声明如下变量:
DataList : TStringlist;
doc : IXMLDOMDocument;
root,child,child1 : IXMLDomElement;
text1,text2 : IXMLDOMText;
nlist : IXMLDOMNodelist;
dataRecord : String;
添加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
.FieldName);
child.appendchild(child1);
//Check field types
case TFieldType(Ord(table.Fields.DataType)) of
ftString:
begin
if Table.Fields.AsString ='' then
temp :='null' //Put a default string
else
temp := table.Fields.AsString;
end;
ftInteger, ftWord, ftSmallint:
begin
if Table.Fields.AsInteger > 0 then
temp := IntToStr(table.Fields.AsInteger)
else
temp := '0';
end;
ftFloat, ftCurrency, ftBCD:
begin
if table.Fields.AsFloat > 0 then
temp := FloatToStr(table.Fields.AsFloat)
else
temp := '0';
end;
ftBoolean:
begin
if table.Fields.Value then
temp:= 'True'
else
temp:= 'False';
end;
ftDate:
begin
if (not table.Fields.IsNull) or
(Length(Trim(table.Fields.AsString)) > 0) then
temp := FormatDateTime('MM/DD/YYYY',
table.Fields.AsDateTime)
else
temp:= '01/01/2000'; //put a valid default date
end;
ftDateTime:
begin
if (not table.Fields.IsNull) or
(Length(Trim(table.Fields.AsString)) > 0) then
temp := FormatDateTime('MM/DD/YYYY hh:nn:ss',
Table.Fields.AsDateTime)
else
temp := '01/01/2000 00:00:00'; //Put a valid default date and time
end;
ftTime:
begin
if (not table.Fields.IsNull) or
(Length(Trim(table.Fields.AsString)) > 0) then
temp := FormatDateTime('hh:nn:ss',
table.Fields.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
if makeXml(table1)=1 then
showmessage('XML Generated')
else
showmessage('Error while generating XML File');
end;
如果你用IE 5.0(或以上版本)打开生成的country.xml文件,它看起来会成下面的样子
- <country>
- <Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
- <Records>
<Name>Bolivia</Name>
<Capital>La Paz</Capital>
<Continent>South America</Continent>
<Area>1098575</Area>
<Population>7300000</Population>
</Records>
.
.
.
- <Records>
<Name>Venezuela</Name>
<Capital>Caracas</Capital>
<Continent>South America</Continent>
<Area>912047</Area>
<Population>19700000</Population>
</Records>
</country>
插入数据
你已经将country表中存在的数据生成了XML文件。因此在这个XML文件中的数据就与country表中是一样的。如果你想将XML文件中的数据插入进country表中又不想删除原来存在的数据的话,将会有主键冲突的错误出现。因此必须先将country表中已经存在的数据删除掉。
添加另一个按钮和一个memo构件于主窗体。在button2的onclick事件中添加如下代码.memo用来显示数据插入中的状态(成功/失败)。
procedure TForm1.Button2Click(Sender: TObject);
var
i,ret_val,count:Integer;
strData:String;
begin
//Before inserting data in to the country table,make sure that the data in
//the generated xml file(country.xml) and country table(DBDEMOS) are
//different.
try
count:=1;
DataList:=TStringList.Create;
memo1.Clear;
doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument;
//Load country.xml file
doc.load('country.xml');
nlist:=doc.getElementsByTagName('Records');
memo1.lines.append('Table Name :country');
memo1.lines.append('---------------------');
for i:=0 to nlist.Get_length-1 do
begin
travelChildren(nlist.Get_item(i).Get_childNodes);
//Removes the first character(,) from dataRecord
strData:=copy(dataRecord,2,length(dataRecord));
memo1.lines.append(strData);
dataRecord:='';
ret_val:=insertintotable(Datalist);
if ret_val=1 then
memo1.lines.append('Data inserted successfully.............!')
else if ret_val=-1 then
memo1.lines.append('Error while updating.....Try again.....!');
memo1.lines.append('============================================='
+'==(Record no. :'+inttostr(count)+')');
DataList.Clear;
count:=count+1;
end;
except
on e:Exception do
Showmessage(e.message);
end;
end;
nlist(refer above program) contains a list of nodes.In our case the first node list is...
<Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
我们传送此节点列表给一个递归函数,travelchildren。它将递归地沿着节点列表查找文本数据,并将此数据加入TStringList(Datalist)变量中。当完成第一轮后,Datalist中将会包含字符串 Argentina,Buenos Aires,South America,2777815,32300003.最后我们将此stringlist传送给函数 insertintotable,它将完成将一条记录插入 country 表的工作。重复此过程即可完成整个XML文件数据的插入工作。
procedure TForm1.travelChildren(nlist1:IXMLDOMNodeList);
var
j:Integer;
temp:String;
begin
for j:=0 to nlist1.Get_length-1 do
begin
//node type 1 means an entity and node type 5 means EntityRef
if((nlist1.Get_item(j).Get_nodeType= 1) or (nlist1.Get_item(j).Get_nodeType=5)) then
travelChildren(nlist1.Get_item(j).Get_childNodes)
//node Type 3 means a text node,ie you find the data
else if(nlist1.Get_item(j).Get_nodeType=3) then
begin
temp:= trim(nlist1.Get_item(j).Get_nodeValue);
dataRecord:=dataRecord+','+temp; //this is for displaying a single record on the memo
DataList.Add(temp); //Datalist will contain one record after completing one full travel through the node list
end
end;
end;
function TForm1.insertintotable(stpt:TStringList):Integer;
var
i:Integer;
begin
table1.close;
table1.open;
table1.Insert;
for i := 0 to stpt.Count - 1 do
begin
table1.Fields.AsVariant:= stpt;
end;
try
table1.post;
result:=1;
except
on E:Exception do
result:=-1;
end;
end;
结论:
你可以将此程序推广至任何数据库,由此数据可以通过XML文件在网络(即使是internet)中传输并在其实终端上更新数据库。我在生成XML文件中还未考虑特殊字符如 &,<,>,',''等等。你可以在生成带这些字符的XML文件时作适合自己需要的改变.
////////////////////////////////////////////////////////////////////////////////
http://www.delphibbs.com/keylife/iblog_show.asp?xid=6301
作者?: alva_bai
标题?: Delphi 6 XML
关键字:
分类?: 开发技巧
密级?: 公开
center][red][h3]三、Delphi 6 XML映象工具[/h3][/red][/center]
[blue][h4]本文是三篇论述Delphi 6中XML功能系列文章的第三篇,也是最后一篇,论述Delphi 6中的XML映象工具,也称之为XML映象器(XML Mapper)。[/h4][/blue]
Delphi 6 含有许多更新更强的XML支持功能,增加了XML文件编程,XML数据绑定向导,XML映象和BizSnap(SOAP/XML Web服务)。本文是三篇论述Delphi 6中XML功能系列文章的第三篇,也是最后一篇,论述Delphi 6中的XML映象工具,也称之为XML映象器(XML Mapper)。
XML映象器
可以单独打开或者在IDE的工具(Tools)菜单中打开映象器(XML Mapper)。这个工具将XML文件影射为类似于数据库中的数据集(DataSet)!在前二篇文章中,我们都使用TXMLDocument组件来处理XML文件或XML绑定的数据。现在,我们不再使用TXMLDocument组件了。我们用XML映象器将XML文件转换成数据包(或反之),这样就可以象我们非常熟悉得那样使用诸如DataSet这样的组件来处理XML数据。
XML映象器可以打开XML文件和数据包文件(*.xml,*.cds),纲文件(*.dtd,*.xdr,*.xsd),库文件(*.xrp)和转换文件(*.xtr)。后面这个转换文件是由映象器自行产生的。现在加载我们在前二篇文章中用到的Clinic.xml文件,看看数据影射成什么样。
如图1所示,Clinic.xml影射为一个Clinics结构,含有若干Clinic子项。每个Clinic有4个域:@No, Title, Date, 和Topics。注意@No是属性,其他3个是元素。
将鼠标移至Clinic节点,按右键,选择"Select All"(Ctrl+A)或者"Select All Children",这样就把@No, Title, Date and Topics各项加到中间的映象表内。此时还没有生成相应的数据集域。
我们再次到Clinic节点按下鼠标右键,选择"Create Datapacket from XML"(Ctrl+D),这时产生了相应的数据包并显示在映象器的右方,如图2所示。
映象器将XML文件转换成4个标准格式的数据包。我们还可以增加或删除数据包。这在B2B场合尤为重要,将接收到的数据(纪录)插入到数据库中。
创建并测试转换
按下Create and Test Transformation按钮,随即弹出一个DBGrid窗口,以ClientDataSet数据包格式显示Clinic.xml文件里的数据。数据转换之后,有4种方式存盘。对于初学者来说,可以存回原先的Clinic.xml文件。另外还可以存为包数据文件格式(*.xml),库文件(repository)格式(.xrp)和转换文件格式(*.xtr)。后面的库文件可以被XMLTransform组件(在Data Access标签里)调用。
XML转换
Delphi 6里有3个XML转换组件。TXMLTransformProvider组件通过clinic.xtr文件将原始XML文件转换到ClientDataSet。
举例来说,在Delphi 6的主窗体上安放一个TXMLTransformProvider(在Data Access标签里)。将TransformRead属性的TransformationFile子属性设为clinic.xtr作为数据源。将XMLDataFile设为clinic.xml文件。
然后安放一个ClientDataSet组件,将它的ProviderName属性设为XMLTransformProvider。
现在可以激活ClientDataSet组件了,它将由XMLTransformProvider提供的数据转换过来。它的PacketRecords属性值缺省为-1,表示转换全部XML文件。
要显示数据,我们再加入DataSource和DBGrid组件。将DataSource指向ClientDataSet,DBGrid指向DataSource,就象我们通常使用得那样。结果显示如图3。
现在可以象使用数据库数据那样使用XML文件里的数据了。如果对数据进行了修改,可以调用ClientDataSet.ApplyUpdates方法将改动的数据存回XML文件。
但是这样做是不够的。我们还得启动XML映象器,将创建方式由"XML to Datapacket"改为"Datapacket to XML"。这时如果按下"Create and Test Transformation"键,看到的是空白页。然后将转换结果存为ClinicToXml.xtr文件。
现在,将TXMLTransformationProvider的TransformationWrite属性中的子属性TransformationFile设为ClinicToXml.xtr文件。
最后在主窗体的OnClose事件中加上存盘指令:
ClientDataSet1.ApplyUpdates(-1)
程序退出之前会将作出的数据修改存回XML文件。
这样就为Delphi 6的B2B开发环境BizSnap的虚拟中间层作好了数据准备。
想看图:http://www.gois.ws/showfile.asp?id=53:default
少爷 (2002-10-22 9:00:00)
[red]2、Delphi 6 XML数据绑定
[/red]
[blue]本文是三篇论述Delphi 6中XML功能系列文章的第二篇,论述Delphi 6中的XML数据绑定(XML Data Binding)。 [/blue]
Delphi 6 含有许多更新更强的XML支持功能,增加了XML文件编程,XML数据绑定向导,XML映象和BizSnap(SOAP/XML Web服务)。我在上一篇文章论述了Delphi 6中的XML文件编程(XML Document Programming)。本文是三篇论述Delphi 6中XML功能系列文章的第二篇,论述Delphi 6中的XML数据绑定(XML Data Binding)。
XML文件编程
XMLDocument组件让我们能够遍历和编辑XML文件。但是在上一篇文章中我提到了,我们只能与无类型节点打交道,必须记住节点元素的名字。这意味着无法进行实时编译调试!幸亏的是,如果Delphi只能处理这样简单的问题就不成其为Delphi了。运用XML的内容相关结构可以做更高级的应用,这就是Delphi 6的XML数据绑定向导(XML Data Binding Wizard)。
XML数据绑定
在Delphi 6的模块仓库(Object Repository)中可以找到XML数据绑定向导(XML Data Binding Wizard)。程序员能够用它生成相应的接口和类来访问与修改XML文件数据,诸如ClientDataSetXML数据,ADO XML数据,其它XML文件数据(如我们在前文用到的Clinic.xml,本文继续使用这个简单的XML文件作示例)。
现在开始吧,启动Delphi 6,在主菜单上选择File | New - Other,然后在仓库中选择XML Data Binding,如图1所示。
向导有三个页面。第一页定义XML纲(Schema)或XML文件(本例用Clinic.xml),如图2所示。
在资源输入框内输入XML纲(Schema)或XML文件。“选项”(Options)对话框定义编码选项和数据类型映射关系(Data Type map)。以后我们还会谈到这些选项。
向导的第二页显示了树结构和节点数据类型(亦即向导生成了些什么样的代码)。图3可以看到我的XML文件结构。
可以看到XML文件里描述的嵌套节点(ClinicsType与ClinicType)和单节点(String)。这时可以打开选项(Options)对话框(图4),修改编码(比如修改前缀)和数据类型映射。
向导的第三页显示生成的相应接口和类。可以把这些结果保存到文件(例如生成Clinic.xdb)。
结果(存储为Clinic.xdb文件)显示如下。我们得到一个ClinicsType类型的Clinics元素,其中包括ClinicType类型的Clinic系列元素。
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xdb="http://www.borland.com/schemas/delphi/6.0/XMLDataBinding">
<xs:element name="Clinics" type="ClinicsType"/>
<xs:complexType name="ClinicsType">
<xs:annotation>
<xs:appinfo xdb:docElement="Clinics"/>
</xs:annotation>
<xs:sequence>
<xs:element name="Clinic" type="ClinicType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ClinicType">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Date" type="xs:string"/>
<xs:element name="Topics" type="xs:string"/>
</xs:sequence>
<xs:attribute name="No" type="xs:string"/>
</xs:complexType>
</xs:schema>
文件同时定义ClinicType类型的Clinic元素包含一系列字符串元素(Title, Date和Topics)。
生成代码
向导生成的代码可以直接在应用中使用。不幸的是,Delphi 6有时会产生“非法操作”的出错信息。重新执行一遍,它又能工作了。
以下是生成的代码(Clinic.pas):
{****************************************************}
{ }
{ Delphi XML Data Binding }
{ }
{ Generated on: 2001/11/07 00:37:00 }
{ Generated from: D:/D6Clinic/src/Clinic.xml }
{ Settings stored in: D:/D6Clinic/src/Clinic.xdb }
{ }
{****************************************************}
unit Clinic;
interface
uses xmldom, XMLDoc, XMLIntf;
type
{ Forward Decls }
IXMLClinicsType = interface;
IXMLClinicType = interface;
{ IXMLClinicsType }
IXMLClinicsType = interface(IXMLNodeCollection)
['{06723E03-662D-11D5-81CE-00104BF89DAD}']
{ Property Accessors }
function Get_Clinic(Index: Integer): IXMLClinicType;
{ Methods & Properties }
function Add: IXMLClinicType;
function Insert(const Index: Integer): IXMLClinicType;
property Clinic[Index: Integer]: IXMLClinicType
read Get_Clinic; default;
end;
{ IXMLClinicType }
IXMLClinicType = interface(IXMLNode)
['{06723E04-662D-11D5-81CE-00104BF89DAD}']
{ Property Accessors }
function Get_No: WideString;
function Get_Title: WideString;
function Get_Date: WideString;
function Get_Topics: WideString;
procedure Set_No(Value: WideString);
procedure Set_Title(Value: WideString);
procedure Set_Date(Value: WideString);
procedure Set_Topics(Value: WideString);
{ Methods & Properties }
property No: WideString read Get_No write Set_No;
property Title: WideString read Get_Title write Set_Title;
property Date: WideString read Get_Date write Set_Date;
property Topics: WideString read Get_Topics write Set_Topics;
end;
{ Forward Decls }
TXMLClinicsType = class;
TXMLClinicType = class;
{ TXMLClinicsType }
TXMLClinicsType = class(TXMLNodeCollection, IXMLClinicsType)
protected
{ IXMLClinicsType }
function Get_Clinic(Index: Integer): IXMLClinicType;
function Add: IXMLClinicType;
function Insert(const Index: Integer): IXMLClinicType;
public
procedure AfterConstruction; override;
end;
{ TXMLClinicType }
TXMLClinicType = class(TXMLNode, IXMLClinicType)
protected
{ IXMLClinicType }
function Get_No: WideString;
function Get_Title: WideString;
function Get_Date: WideString;
function Get_Topics: WideString;
procedure Set_No(Value: WideString);
procedure Set_Title(Value: WideString);
procedure Set_Date(Value: WideString);
procedure Set_Topics(Value: WideString);
end;
{ Global Functions }
function GetClinics(Doc: IXMLDocument): IXMLClinicsType;
function LoadClinics(const FileName: WideString): IXMLClinicsType;
function NewClinics: IXMLClinicsType;
这里有二个接口类型:IXMLClinicsType和IXMLClinicType;用二个类(TXMLClinicsType和TXMLClinicType)来执行这二个接口。另外还有三个全局函数:GetClinics (获得根元素),LoadClinics (从外部XML文件加载)和NewClinics (在内存生成新文件)。
用法
使用生成的Clinic.pas单元是很容易的。跟前一篇文章的做法一样,使用XMLDocument组件(在Inernet标签内)。不过我们不再使用无类型节点了,我们可以调用GetClinics函数获得IXMLClinicsType类型。以下是具体操作过程:
在Delphi 6建立一个新的应用(project)
在XML数据绑定向导指引下建立Clinic.pas文件(经过命名存盘 - 译者)
在主窗体上加入一个XMLDocument组件,其FileName属性为Clinic.xml
在主窗体的OnCreate事件中加入以下代码:
procedure TForm1.FormCreate(Sender: TObject);
var
Clinics: IXMLClinicsType;
begin
Clinics := GetClinics(XMLDocument1);
end;
把Clinics变量放到主窗体中是很有用的,这样就可以在主窗体运行期间使用Clinics接口。使用IXMLClinicsType变量类型要比以前使用普通XMLDocument组件方便多了。现在可以通过Get_Clinic方法来获得各个Clinic元素,还可以在特定位置插入新的Clinic元素。用Clinics.Clinic可以获得节点元素,用Getter和Setter方法可以得到或设置元素值。现在可以直接访问No, Title, Date, Topics等属性了:
procedure TForm1.ButtonGetClick(Sender: TObject);
var
Clinic: IXMLClinicType;
begin
Clinic := Clinics.Clinic[0];
EditNo.Text := Clinic.No;
EditTitle.Text := Clinic.Title;
EditDate.Text := Clinic.Date;
EditTopics.Text := Clinic.Topics
end;
可以在Clinic.pas中看到,Getter和Setter是方法而不是属性(实际上,我始终认为使用属性更清楚些)。但是Delphi 6让你看到的却是属性描述而不是方法本身(Delphi 6的另一个受欢迎的优点)。将上面这段代码与前一篇文章使用的方法相比较,就能感到操作方便多了。
下面的例子在XML树的末尾加入一个节点:
procedure TForm1.ButtonAddClick(Sender: TObject);
begin
with Clinics.Add do
begin
No := '2001-2-8; // 8th Clinic of the 2nd series of 2001
Title := 'Special Kylix 2 Clinic';
Date := '2001/12/21';
Topics := 'Kylix 2 New Features'
end
end;
如果没有把XMLDocument组件的AutoSave设置为真,可以用以下方法保存更动结果:
procedure TForm1.FormDestroy(Sender: TObject);
begin
XMLDocument1.SaveToFile;
end;
这就是XML数据绑定向导,一个非常方便的方法。它能做得越来越好。
下一篇文章:
我们已经看到了XML数据绑定的优点。不过好象还有点不“满足”,比方要遍历各个节点,存取节点值(不单单是字符串类型),虽然可用选项决定,但还是用Delphi 6的XML映象更好,它的功能更强。我们将在下一篇文章论述。
图:http://www.gois.ws/showfile.asp?id=52:default
少爷 (2002-10-22 9:07:00)
[red][h3]一、Delphi 6 XML文件编程[/h3][/red]
[blue][h4]Delphi 6提供的XML功能增加了XML文件编程,XML数据绑定向导,XML映象和BizSnap(SOAP/XML Web服务)。本文是三篇论述Delphi 6中XML功能系列文章的第一篇,论述Delphi 6中的XML文件编程。[/h4][/blue]
Delphi 6 含有许多更新更强的XML支持功能。Delphi早期版本中提到的XML只在说明MIDAS数据格式和XMLBroker组件(在ClientDataSet和ADODataSet组件中也提到过)。
Delphi 6提供的XML功能增加了XML文件编程,XML数据绑定向导,XML映象和BizSnap(SOAP/XML Web服务)。本文是三篇论述Delphi 6中XML功能系列文章的第一篇,论述Delphi 6中的XML文件编程(XML Document Programming)。
XML文件
就在前几年XML刚刚问世的时候,它就被赋予电子商务的世界语的美称,尤其在B2B(business-2-business)领域内更是如此。原因就是XML是个非常简单的而又是结构化的ASCII文本语言,任何人和任何事都能读懂它。与作为当前网页标准格式的HTML语言不一样。两者之间的不同之处在于:HTML要用预先定义的语法集来解释;XML文件虽然要遵循通用的语法规则,但真正的关键词(标记)则是由作者决定的。其含义可由DTD(Document Type Definition)文件或者现在更为流行使用的纲(Schema)来选择定义。使用DTD或Schema的XML文件可以说是一个自我解释的文件,在数据集成化和出错调整时都很有用。
XML的结构化模式使得数据和信息可以在应用程序之间相互传递(包括在多层应用的中间层之间,例如Delphi提供的功能)。它提供一个标准格式,对所用的通讯协议是透明的。这就是XML在电子商务应用的电子数据交换(Electronic Data Interchange, EDI)和B2B中扮演重要角色的原因。通过DTD或者Schema来“定义”(或检查)XML文件理所当然地使得任何人都可以与任何其它人和事交谈,只要使用恰当的的XML/DTD/Schema组合。现在越来越经常要求集成现有的系统,XML可能成为系统之间相互交谈和理解的“语言”。
本文不讨论涉及EDI或B2B的XML。但它引出三篇论及XML文件编程和Delphi 6的XML新特性的文章。
XML文件编程
Delphi 6 支持DOM解析,因此我们可以读(和翻译!)以及编辑任何XML文件,甚至在缺少了DTD或Schema的情况下,一如本文所示。我在本文创建了一个包含我的网页内容的小小的XML文件:
<?xml version="1.0" standalone="yes" ?>
<Clinics>
<Clinic No="2002-1">
<Title>dbExpress and DataSnap</Title>
<Date>2002/01/10</Date>
<Topics> </Topics>
</Clinic>
<Clinic No="2002-2">
<Title>WebBroker/InternetExpress and WebSnap</Title>
<Date>2002/01/31</Date>
<Topics> </Topics>
</Clinic>
<Clinic No="2002-3">
<Title>WebSnap and Adapters</Title>
<Date>2002/02/21</Date>
<Topics> </Topics>
</Clinic>
<Clinic No="2002-4">
<Title>BizSnap and WebServices</Title>
<Date>2002/03/14</Date>
<Topics> </Topics>
</Clinic>
<Clinic No="2002-5">
<Title>WebSnap and BizSnap</Title>
<Date>2002/04/04</Date>
<Topics> </Topics>
</Clinic>
</Clinics>
这个XML文件将作为这组系列文章的例子(本文以及后面的XML数据绑定和XML映像等文章里都会用到)。
TXMLDocument组件
要进行XML文件编程就要用到TXMLDocument组件(在Delphi 6组件栏的Internet标签里)。可以从那里拖拽一个组件到窗体或数据模块上。TXMLDocument组件有些值得一提的属性。显然,Active属性可以用于打开XML文件,但我们这会儿还用不上。DOMVendor属性定义了XML文件的解析器。在我的机器上,被置为"MSXML"。可以自己插入其它类型的DOMVendor(实际就是任何能够执行IDOMImplementation的接口组件)。在使用自己的DOMVendor组件前,要对它先进行注册。DOMVendor是一个全局变量,含有一个DOMVendor注册值表。这样第三方的DOMVendor可以在注册后加入这个表,让用户可以自行选择TXMLDocument的DOMVendor属性值。
TXMLDocument的第三个属性是FileName,指向XML文件(本例为clinics.xml)。如果XML文件并不是直接存在,而是通过其它转换获得,也可以设置这个属性值[译者注:只要使用的解析器支持,甚至可以指向一个URL]。这是在EDI和B2B里的基本用法,这种场合下往往不必[有时也不可能 - 译者]存储真正的XML文件。
NodeIndentStr属性决定子节点的后移位置,缺省值为二个空格,最多可选到八个空格。这个属性只有在选项里设置了doNodeAutoIndent标识为真(True)后才有效,这个标识的缺省状态为非真(False)。组件选项里还有其它标识,如NodeAutoCreate, AttrNull, AutoPrefix, NamespaceDecl和AutoSave。AutoSave使得组件在关闭时能够将XML文件发生的变动自动存盘。我觉得这是个非常好的特性,所以在我的例子里,我把它设置为真(它的缺省状态是非真)。
除了常规选项之外,XMLDocument组件还有解析选项,如ResolveExternals, ValidateOnParse, PreserveWhiteSpace和AsyncLoad,这些标识的意义从名字上就可以看出。
XMLDocument组件的最后一个属性是XML,指向一条XML的字符串(点击属性旁边的省略号可以编辑XML)。正如我前面说过的,这在B2B多层应用中是非常有用的。这时中间层往往接收从其它应用中得到XML字符串,就要对这些XML数据进行处理或“编程”。
XML文件编程
只要稍作设置(设置FileName为clinics.xml和将doAutoSave置为真),就可以激活TXMLDocument组件。激活之后,就可以遍历节点,读取和编辑数据。
现在我们可以在XMLDocument里访问各个节点(IXMLNode),递归访问各节点的子节点。例如,可以用一个按钮来获取第一个节点的元素值并将结果写入Memo:
procedure TForm1.Button1Click(Sender: TObject);
var
Clinic: IXMLNode;
begin
Clinic := XMLDocument1.DocumentElement.ChildNodes[0];
Memo1.Lines.Clear;
Memo1.Lines.Add(Clinic.ChildNodes['Title'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Date'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Topics'].Text)
end;
增加索引指针可以访问其它节点。下面的例子用"current"作索引,通过循环增加"current"的值来遍历节点。我用try-except来防止循环出界。
procedure TForm1.Button2Click(Sender: TObject);
var
Clinic: IXMLNode;
begin
Inc(current);
try
Clinic := XMLDocument1.DocumentElement.ChildNodes[current];
Memo1.Lines.Clear;
Memo1.Lines.Add(Clinic.ChildNodes['Title'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Date'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Topics'].Text);
except
on E: Exception do
Memo1.Lines.Add(E.Message)
end
end;
除了遍历节点之外,还可以修改子节点的值。下面的代码将第一个节点的标题值加上一个"HOT"前缀。
procedure TForm1.Button1Click(Sender: TObject);
var
Clinic: IXMLNode;
begin
current := 0;
Clinic := XMLDocument1.DocumentElement.ChildNodes[current];
Memo1.Lines.Clear;
Clinic.ChildNodes['Title'].Text := 'HOT: ' + Clinic.ChildNodes['Title'].Text;
Memo1.Lines.Add(Clinic.ChildNodes['Title'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Date'].Text);
Memo1.Lines.Add(Clinic.ChildNodes['Topics'].Text);
end;
还可以增删节点。下面的代码增加一个子节点并赋予缺省值:
procedure TForm1.Button1Click(Sender: TObject);
var
Clinic: IXMLNode;
begin
Clinic := XMLDocument1.DocumentElement.AddChild('Clinic');
Clinic.ChildNodes['Title'].Text := 'Title';
Clinic.ChildNodes['Date'].Text := 'Date';
Clinic.ChildNodes['Topics'].Text := 'Topics';
end;
记住,我们已经将AutoSave设置为真。所以在XMLDocument对象或应用关闭时,任何变化都将自动存盘。也可以调用XMLDocument1.SaveToFile方法显式存盘。
下一篇文章:XML数据绑定
XMLDocument让我们能够访问XML节点并编辑节点。但我们只能与无类型节点打交道,必须记住节点元素的名字。用上面的代码可以漂亮地处理小型的XML文件,但是,Delphi如果只能处理这样简单的问题就不成其为Delphi了。运用XML的内容相关结构可以做更高级的应用,这就是Delphi 6提供的XML数据绑定特性。我们将在下一篇文章中论述。
图:http://www.gois.ws/showfile.asp?id=51:default
少爷 (2002-10-22 9:13:00)
[red][h3]用XML文件作数据库转换中介[/h3][/red]
[blue][h4]本文提供的Delphi代码可以将XML文件作为数据库转换的中间介质。[/h4][/blue]
本文说明如何从一个Paradox数据库读取数据,生成XML文件,然后将XML文件中的数据插入到一个新的表中。这个新的数据库表可以还是Paradox,也可以是任何Delphi能够访问的数据库。我使用MSXML来解析XML文件,因此你必须使用IE 5.0以上才能使用我的代码。
生成XML文件
转换生成XML文件的过程如下。
XML文件的根名字与表名相同(本例是"country")。
标记<Records>和</Records>之间是各个纪录。
XML中节点元素标记<Field Name>和</Field Name>之间是数据。
<country>
<Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
.
.
</country>
启动一个新应用(application),在主窗口(Main Form)中加入按钮(TButton)和表(TTable)控件。Tabled的设置如下:
DatabaseName : DBDEMOS
Name : Table1
TableName : country (不要后缀 ".db")
Active : True
选择主菜单Project/Import,导入数据库类型。
选择"Microsoft XML, Version 2.0 (version 2.0)",然后点击"Create Unit"按钮。此后,就有一个MSXML_TLB增加到你的工程(project)文件中。要在unit文件的interface段的uses说明里加上MSXML_TLB。
在变量声明中作如下声明:
DataList : TStringlist;
doc : IXMLDOMDocument;
root, child, child1 : IXMLDomElement;
text1, text2 : IXMLDOMText;
nlist : IXMLDOMNodelist;
dataRecord : String;
在执行部分加入以下代码,这就可以从country表中读取数据并生成XML文件。
function TForm1.makeXml(table:TTable):Integer;
var
i : Integer;
xml : String;
begin
try
table.close;
table.open;
xml := table.TableName;
doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument;
// 设置XML文件的根名字为"country",与表名相同。
root := doc.createElement(xml);
// 把根节点加入到文件中
doc.appendchild(root);
// 循环产生整个XML文件
while not table.eof do
begin
// 加入第一级子节点,即纪录
child:= doc.createElement('Records');
root.appendchild(child);
// 加入第二级子节点
for i:=0 to table.FieldCount-1 do
begin
child1:=doc.createElement(table.Fields.FieldName);
child1.appendChild(doc.createTextNode(table.Fields.value));
child.appendchild(child1);
end;
table.Next;
end;
doc.save(xml+'.xml');
Result:=1;
except
on e:Exception do
Result:=-1;
end;
end;
Call the function in Button1's click event:
procedure TForm1.Button1Click(Sender: TObject);
begin
if makeXml(table1)=1 then
showmessage('XML Generated')
else
showmessage('Error while generating XML File');
end;
随后用IE浏览器打开XML文件(country.xml),就能看到如下结果:
<country>
<Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
<Records>
<Name>Bolivia</Name>
<Capital>La Paz</Capital>
<Continent>South America</Continent>
<Area>1098575</Area>
<Population>7300000</Population>
</Records>
.
.
<Records>
<Name>Venezuela</Name>
<Capital>Caracas</Capital>
<Continent>South America</Continent>
<Area>912047</Area>
<Population>19700000</Population>
</Records>
</country>
生成新表
XML文件中的数据与原表的数据完全一样。如果直接将数据插入回原表,会导致主索引出错。所以如果真想这么作得话,要事先删除原表。
在主窗口上增加另一个按钮和一个memo控件。为第二个按钮填写以下onclick事件代码。(memo用于显示数据插入成功与否的信息)。
procedure TForm1.Button2Click(Sender: TObject);
var
i,ret_val,count:Integer;
strData:String;
begin
// 注意:在向country表插入数据前,确认XML文件中的数据与原表数据是不同的。或者作一个新表。
try
count:=1;
DataList:=TStringList.Create;
memo1.Clear;
doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument;
// 加载country.xml文件
doc.load('country.xml');
nlist:=doc.getElementsByTagName('Records');
memo1.lines.append('Table Name :country');
memo1.lines.append('---------------------');
for i:=0 to nlist.Get_length-1 do
begin
travelChildren(nlist.Get_item(i).Get_childNodes);
// 从dataRecord中滤掉第一个","
strData:=copy(dataRecord,2,length(dataRecord));
memo1.lines.append(strData);
dataRecord:='';
ret_val:=insertintotable(Datalist);
if ret_val=1 then
memo1.lines.append('Data inserted successfully.............!')
else if ret_val=-1 then
memo1.lines.append('Error while updating.....Try again.....!');
memo1.lines.append('=============================================' + '==(Record no. :'+inttostr(count)+')');
DataList.Clear;
count:=count+1;
end;
except
on e:Exception do
Showmessage(e.message);
end;
end;
nlist是节点列表。在本例中,第一个节点是:
<Records>
<Name>Argentina</Name>
<Capital>Buenos Aires</Capital>
<Continent>South America</Continent>
<Area>2777815</Area>
<Population>32300003</Population>
</Records>
我们用travelchildren函数来解析节点。它用递归方式遍历节点列表,直到找到一个文本节点。这个文本数据被加入到TStringlist(Datalist)。第一次遍历完成后,可以在Datalist看到里看到Argentina, Buenos Aires, South America, 2777815, 32300003等字符串。然后把这个字符串传递给insertintotable函数,让它在country表中插入一条纪录。循环这个过程直至完成遍历整个XML文件。
procedure TForm1.travelChildren(nlist1:IXMLDOMNodeList);
var
j:Integer;
temp:String;
begin
for j:=0 to nlist1.Get_length-1 do
begin
// 节点类型1表示是一个实体(entity),节点类型5表示EntityRef
if((nlist1.Get_item(j).Get_nodeType= 1) or (nlist1.Get_item(j).Get_nodeType=5)) then
travelChildren(nlist1.Get_item(j).Get_childNodes)
// 节点类型3表示文本节点,即找到数据
else if(nlist1.Get_item(j).Get_nodeType=3) then
begin
temp:= trim(nlist1.Get_item(j).Get_nodeValue);
dataRecord:=dataRecord+','+temp;
//在memo显示一条纪录
DataList.Add(temp);
// 完成一次节点扫描后包含一条纪录的信息list.
end
end;
end;
function TForm1.insertintotable(stpt:TStringList):Integer;
var
I:Integer;
begin
table1.close;
table1.open;
table1.Insert;
for I := 0 to stpt.Count - 1 do
table1.Fields.AsVariant:=stpt;
try
table1.post;
result:=1;
except
on E:Exception do
result:=-1;
end;
end;
这个程序可以用于任何数据库,因此数据能以XML文件形式在网络(或者在互联网)上传递,实现远程更新。
如有问题,可与我联系。
少爷 (2002-10-22 9:17:00)
[red][h3]创建XML Datasets及动态控件[/h3][/red]
[blue][h4]本文介绍了如何创建XML dataset,然后通过XML文件里的元素对XML“表”动态地创建相应控件。[/h4][/blue]
本文介绍了如何创建XML dataset,然后通过XML文件里的元素对XML“表”动态地创建相应控件。文末所附的工程(Project)文件,使用户可以创建简单的(非关系型)数据库,这个数据库用XML文件存储。随后加载这个XML文件,经解析之后,可以在用户自行定义的“数据库”输入界面上动态生成控件。
大家都习惯使用Delphi来编程,实在太容易了。一桩困难的任务可以变得简单得多,伤透脑筋的结构设计可以轻易完成。感谢Delphi提供的这种灵活实用的工具,和优雅的界面风格。
Delphi 6问世,提供了新的组件,使开发者能够创建和管理XML文件。例如,在Interner组里提供了XMLDocument组件,在Data Access组里提供了XMLTransform, XMLTransformProvider, and XMLTransformClient组件集。
另外,在主菜单的File | New | Other | New下,你能找到XML数据绑定向导(XML Data Binding Wizard)。如果还不够,你可以使用XML映象器(Tools | XML Mapper or Programs | Borland Delphi 6 | XML Mapper)。尽管Delphi已经提供了如此之多的捷径来产生和使用XML文件,Borland公司还是向我们提供了“免动脑筋”的解决方案 - 使用“即试即用”的TClientDataSet。
从TClientDataSet到XML
用TClientDataSet创建和加载XML文件是很容易的,调用SaveToFile和LoadToFile函数即可,只要对函数的第二个参数设置相应的XML类型。实际上,有三个参数类型,一个是遗留的dfBinary二进制类型,另外是dfXMLUTF8和UTF8-based XML类型。
例如,可以这样存储文件(代码中的cds是TClientDataSet,FCurrentDataBaseName是文件名):
cds.SaveToFile(Format('%s.xml', [FCurrentDataBaseName]), dfXMLUTF8);
这样你就得到了XML文件。
动态创建相应的控件
当然,在调用SaveToFile之前,还得创建数据库。这也不难,如下例。假设有两个TListBox控件,一个存放域名(lstbxFields),另一个存放相应的数据类型(lstbxDataTypes),动态创建控件的过程如下:
{----------------------------------------------------}
procedure TfEZDBBrowserAddEditDelete.Cre8Controls;
const
{ DYNAMIC_CONTROLS_TOP:第一个控件与顶部的距离}
DYNAMIC_CONTROLS_TOP = 24;
DYNAMIC_CONTROLS_LEFT = 12;
{ 为简短起见,省略其它常量}
var
i: Integer;
iCurrentRow, iCurrentTop, iControlsOnThisRow, iCurrentWidthOfControls: Integer;
MemoOnThisRow: Boolean;
procedure Cre8StringLabeledEdit(ACaption: String);
begin
with TLabeledEdit.Create(Self) do
begin
Parent := scrlbx;
Name := Format('lbledt%s', [ACaption]);
{ HelpContext用于映射控件和表格索引(grid index 3 = (control with HelpContext 3), 等等)}
HelpContext := HELP_CONTEXT_OFFSET+i;
Hint := ACaption;
EditLabel.Caption := ACaption;
EditLabel.Font.Style := EditLabel.Font.Style+[fsBold];
Text := '';
{ 扩展宽度,使可输入长串}
Width := LABELED_EDIT_STRING_WIDTH;
grdcds.Columns.Width := GRID_COLUMN_ALPHA_WIDTH;
{ 如果当前行宽不合适,去下一行}
if (iCurrentWidthOfControls + LABELED_EDIT_STRING_WIDTH +
(SPACE_BETWEEN_DYNAMICALLY_CREATED_CONTROLS * iControlsOnThisRow)) >
MAX_WIDTH_OF_CONTROLS then
begin
inc(iCurrentRow);
iControlsOnThisRow := 1;
if MemoOnThisRow then
Top := (SPACE_BETWEEN_DYNAMICALLY_CRE8ED_ROWS * iCurrentRow)+MEMO_HEIGHT
else
Top := iCurrentTop+SPACE_BETWEEN_DYNAMICALLY_CRE8ED_ROWS;
iCurrentTop := Top;
MemoOnThisRow := False;
Left := DYNAMIC_CONTROLS_LEFT;
iCurrentWidthOfControls := LABELED_EDIT_STRING_WIDTH;
end
else
begin
Top := iCurrentTop;
if iCurrentWidthOfControls = 0 then
begin
Left := DYNAMIC_CONTROLS_LEFT;
if iCurrentRow=0 then
FFirstDynamicControl := Name;
end
else
Left := iCurrentWidthOfControls + (SPACE_BETWEEN_DYNAMICALLY_CREATED_CONTROLS *
iControlsOnThisRow) + DYNAMIC_CONTROLS_LEFT;
inc(iControlsOnThisRow);
iCurrentWidthOfControls := iCurrentWidthOfControls + LABELED_EDIT_STRING_WIDTH;
end;
OnEnter := DynamicControlEnter;
OnExit := DynamicControlExit;
Tag := LABELED_EDIT_STRING_TAG;
end;
end;
{ 创建Memos有一个特别问题:Memo很长时,要随时保留回车键("crlf")位置,用于调整屏幕顶端位置。}
procedure Cre8Memo(ACaption: String);
var mmoLeft, mmoTop: Integer;
begin
with TMemo.Create(Self) do
begin
Parent := scrlbx;
Name := Format('mmo%s', [ACaption]);
HelpContext := HELP_CONTEXT_OFFSET+i;
Hint := ACaption;
Text := '';
Scrollbars := ssVertical;
{ 如果当前行宽不合适,去下一行}
if (iCurrentWidthOfControls + MEMO_WIDTH + (SPACE_BETWEEN_DYNAMICALLY_CREATED_CONTROLS *
iControlsOnThisRow)) > MAX_WIDTH_OF_CONTROLS then
begin
inc(iCurrentRow);
iControlsOnThisRow := 1;
if MemoOnThisRow then
Top := iCurrentTop + SPACE_BETWEEN_DYNAMICALLY_CRE8ED_ROWS + MEMO_HEIGHT
else
Top := iCurrentTop + SPACE_BETWEEN_DYNAMICALLY_CRE8ED_ROWS;
iCurrentTop := Top;
Left := DYNAMIC_CONTROLS_LEFT;
iCurrentWidthOfControls := MEMO_WIDTH;
end
else
begin
Top := iCurrentTop;
if iCurrentWidthOfControls = 0 then
begin
Left := DYNAMIC_CONTROLS_LEFT;
if iCurrentRow=0 then
FFirstDynamicControl := Name;
end
else
Left := iCurrentWidthOfControls + (SPACE_BETWEEN_DYNAMICALLY_CREATED_CONTROLS *
iControlsOnThisRow) + DYNAMIC_CONTROLS_LEFT;
inc(iControlsOnThisRow);
iCurrentWidthOfControls := iCurrentWidthOfControls + MEMO_WIDTH;
end;
{ 现在可以在行例设置memo了}
MemoOnThisRow := True;
mmoLeft := Left;
mmoTop := Top;
OnEnter := DynamicControlEnter;
OnExit := DynamicControlExit;
Tag := MEMO_MEMO_TAG;
end;
{ 在上面设置标签}
with TLabel.Create(Self) do
begin
Parent := scrlbx;
{ 也许可以不用Name... }
Name := Format('lbl%s', [ACaption]);
Caption := ACaption;
Font.Style := Font.Style+[fsBold];
Left := mmoLeft;
Top := mmoTop-LBL_VERTICAL_DELTA;
end;
end;
procedure Cre8MoneyLabeledEdit(ACaption: String);
{为简短起见,这里省略代码,可到附件下载}
procedure Cre8IntegerLabeledEdit(ACaption: String);
{为简短起见,这里省略代码,可到附件下载}
procedure Cre8FloatLabeledEdit(ACaption: String);
{为简短起见,这里省略代码,可到附件下载}
procedure Cre8ComboBox(ACaption: String);
{为简短起见,这里省略代码,可到附件下载}
begin { 初始化变量}
iCurrentRow := 0;
iCurrentWidthOfControls := 0;
iControlsOnThisRow := 0;
iCurrentTop := DYNAMIC_CONTROLS_TOP;
MemoOnThisRow := False;
dm.cds.Active := True;
{ Do not want to do this in the DM's Create, as the CDS must be active first }
dm.cds.LogChanges := False;
for i := 0 to Pred(dm.CDS.FieldList.Count) do
begin
with dm.CDS.Fields do
begin
if (Fields is TStringField) then
Cre8StringLabeledEdit(Fields.FieldName)
else if (Fields is TMemoField) then
Cre8Memo(Fields.FieldName)
else if (Fields is TCurrencyField) then
Cre8MoneyLabeledEdit(Fields.FieldName)
else if (Fields is TIntegerField) then
Cre8IntegerLabeledEdit(Fields.FieldName)
else if (Fields is TFloatField) then
Cre8FloatLabeledEdit(Fields.FieldName)
else if (Fields is TDateField) then
Cre8DateTimePicker(Fields.FieldName)
else if (Fields is TBooleanField) then
Cre8ComboBox(Fields.FieldName);
end;
end;
Popul8DynamicControls;
end;