我一直在使用IXMLNodeCollection接口和TXMLNodeCollection类时遇到问题。我把问题缩小到了这个范围。考虑一个简单的XML文档,例如:
<?xml version="1.0"?>
<Root id="27">
<SomeItems>
<SomeItem id="69"/>
<SomeItem id="84"/>
<SomeItem id="244"/>
</SomeItems>
</Root>
基本上,Root
节点包含SomeItems
个SomeItem
节点集合。我已将其实现为三个基于IXMLNode / iXMLNodeCollection的接口,以及相关的TXMLNode / TXMLNodeCollection类,如下所示:
unit XNodes;
interface
uses
System.SysUtils, Xml.xmldom, Xml.XMLDoc, Xml.XMLIntf;
type
IXMLAParent = interface;
IXMLSomeItems = interface;
IXMLSomeItem = interface;
{ AParent }
IXMLAParent = interface(IXMLNode)
['{61AB551A-F1B5-437C-A265-9FB4BBBC8A8B}']
{ Property Accessors }
function GetId: Integer;
procedure SetId(Value: Integer);
function GetSomeItems: IXMLSomeItems;
{ Methods & Properties }
property Id: Integer read GetId write SetId;
property SomeItems: IXMLSomeItems read GetSomeItems;
end;
TXMLAParent = class(TXMLNode, IXMLAParent)
protected
function GetId: Integer;
procedure SetId(Value: Integer);
function GetSomeItems: IXMLSomeItems;
public
procedure AfterConstruction; override;
end;
{ SomeItems }
IXMLSomeItems = interface(IXMLNodeCollection)
['{7071899E-8F58-4685-A908-8E4E1C13F556}']
{ Property Accessors }
function GetSomeItem(Index: Integer): IXMLSomeItem;
{ Methods & Properties }
function Add: IXMLSomeItem;
function Insert(const Index: Integer): IXMLSomeItem;
property SomeItem[Index: Integer]: IXMLSomeItem read GetSomeItem; default;
end;
TXMLSomeItems = class(TXMLNodeCollection, IXMLSomeItems)
protected
{ IXMLSomeItems }
function GetSomeItem(Index: Integer): IXMLSomeItem;
function Add: IXMLSomeItem;
function Insert(const Index: Integer): IXMLSomeItem;
public
procedure AfterConstruction; override;
end;
{ SomeItem }
IXMLSomeItem = interface(IXMLNode)
['{4F1FC343-9EAD-4C33-93E1-BD511E59F44A}']
{ Property Accessors }
function GetId: Integer;
procedure SetId(Value: Integer);
{ Methods & Properties }
property Id: Integer read GetId write SetId;
end;
TXMLSomeItem = class(TXMLNode, IXMLSomeItem)
protected
{ IXMLSomeItem }
function GetId: Integer;
procedure SetId(Value: Integer);
public
procedure AfterConstruction; override;
end;
implementation
{ TXMLAParent }
procedure TXMLAParent.AfterConstruction;
begin
RegisterChildNode('SomeItems', TXMLSomeItems);
inherited;
end;
function TXMLAParent.GetId: Integer;
begin
try
Result := AttributeNodes['id'].NodeValue;
except
on Exception do
begin
AttributeNodes['id'].NodeValue := -1;
end;
end;
end;
procedure TXMLAParent.SetId(Value: Integer);
begin
SetAttribute('id', Value);
end;
function TXMLAParent.GetSomeItems: IXMLSomeItems;
begin
try
Result := ChildNodes['SomeItems'] as IXMLSomeItems;
except
on E: Exception do
begin
Result := Self.AddChild('SomeItems') as IXMLSomeItems;
end;
end;
end;
{ TXMLSomeItems }
procedure TXMLSomeItems.AfterConstruction;
begin
RegisterChildNode('SomeItem', TXMLSomeItem);
ItemTag := 'SomeItem';
ItemInterface := IXMLSomeItem;
inherited;
end;
function TXMLSomeItems.GetSomeItem(Index: Integer): IXMLSomeItem;
begin
Result := List[Index] as IXMLSomeItem;
end;
function TXMLSomeItems.Add: IXMLSomeItem;
begin
Result := AddItem(-1) as IXMLSomeItem;
end;
function TXMLSomeItems.Insert(const Index: Integer): IXMLSomeItem;
begin
Result := AddItem(Index) as IXMLSomeItem;
end;
{ TXMLSomeItem }
procedure TXMLSomeItem.AfterConstruction;
begin
inherited;
end;
function TXMLSomeItem.GetId: Integer;
begin
Result := AttributeNodes['id'].NodeValue;
end;
procedure TXMLSomeItem.SetId(Value: Integer);
begin
SetAttribute('id', Value);
end;
end.
现在在我的应用中,我使用IXMLDocument
作为根节点创建IXMLParent
,然后我想在该文档的SomeItems
集合中创建一些节点。这是我添加单个节点的代码
par := NewXMLDocument.GetDocBinding('Root', TXMLAParent, '') as IXMLAParent;
par.OwnerDocument.Options := par.OwnerDocument.Options + [doNodeAutoIndent];
par.Id := 27;
WriteLn('NewXML:');
WriteLn(par.XML);
WriteLn('NumChild: ', par.SomeItems.Count);
item := par.SomeItems.Add;
item.Id := 69;
WriteLn('Add Item:');
WriteLn(par.XML);
WriteLn('NumItems: ', par.SomeItems.Count);
try
WriteLn('Does this fail? ', par.SomeItems[0].Id);
except
WriteLn('Yes, that failed!');
end;
而不是打印NumItems: 1
,它说有4个项目。此外,尝试访问集合中的第一个节点时会抛出异常!似乎par.SomeItems[0]
构造不返回IXMLSomeItem引用。为什么呢?
真奇怪的是,如果我保存XML文档,然后重新加载它,它现在可以工作了!这是整个计划:
程序测试;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, ActiveX, Xml.xmldom, Xml.XMLDoc, Xml.XMLIntf, XNodes;
var
par: IXMLAParent;
item: IXMLSomeItem;
doc: IXMLDocument;
begin
CoInitialize(nil);
par := NewXMLDocument.GetDocBinding('Root', TXMLAParent, '') as IXMLAParent;
par.OwnerDocument.Options := par.OwnerDocument.Options + [doNodeAutoIndent];
par.Id := 27;
WriteLn('NewXML:');
WriteLn(par.XML);
WriteLn('NumChild: ', par.SomeItems.Count);
item := par.SomeItems.Add;
item.Id := 69;
WriteLn('Add Item:');
WriteLn(par.XML);
WriteLn('NumItems: ', par.SomeItems.Count);
try
WriteLn('Does this fail? ', par.SomeItems[0].Id);
except
WriteLn('Yes, that failed!');
end;
doc := par.OwnerDocument;
doc.Active := True;
doc.Options := doc.Options + [doNodeAutoIndent];
doc.SaveToFile('temp.xml');
par := nil;
par := LoadXMLDocument('temp.xml').GetDocBinding('Root', TXMLAParent, '') as IXMLAParent;
par.OwnerDocument.Options := par.OwnerDocument.Options + [doNodeAutoIndent];
WriteLn('OpenXML:');
WriteLn(par.XML);
WriteLn('NumItems: ', par.SomeItems.Count);
WriteLn('This works: ', par.SomeItems[0].Id);
par := nil;
CoUninitialize;
end.
以下是该程序的整个输出:
NewXML:
<Root id="27"/>
NumChild: 0
Add Item:
<Root id="27">
<SomeItems>
<SomeItem id="69"/>
</SomeItems>
</Root>
NumItems: 4
Yes, that failed!
OpenXML:
<Root id="27">
<SomeItems>
<SomeItem id="69"/>
</SomeItems>
</Root>
NumItems: 1
This works: 69
(请注意,这个程序最后抛出一个异常;我不关心这个,因为这是一个人为的演示。而且,奇怪的增加缩进很烦人,但并不重要;如果你知道如何防止这种情况,好吧,奖励积分!)这种保存/阅读解决方法在过去有效,但在实践中它很笨拙。我真的希望能够添加到IXMLNodeCollection
然后能够在创建它们之后立即访问代码中的节点。有人可以解释我做错了吗?
答案 0 :(得分:3)
而不是打印NumItems:1,它说有4个项目。此外,尝试访问集合中的第一个节点时会抛出异常! par.SomeItems [0]构造似乎没有返回IXMLSomeItem引用。为什么呢?
一个词:空白。
您正在启用doNodeAutoIndent
标志,该标志在元素之间插入缩进空格。 XML表示使用DOM中的额外节点的空白,例如:
Root |_ whitespace |_ SomeItems |_ whitespace |_ SomeItem |_ whitespace |_ SomeItem |_ whitespace |_ SomeItem |_ whitespace
真奇怪的是,如果我保存XML文档,然后重新加载它,它现在可以工作了!
当您加载XML文档时,如果(T|I)XMLDocument.ParseOptions
属性未启用poPreserveWhiteSpace
标志,则解析器会丢弃空白,因此DOM中不存在这些额外的空白节点。
Root |_ SomeItems |_ SomeItem |_ SomeItem |_ SomeItem
请注意,此程序最后会抛出异常
在调用CoUnitialize()
之前,您没有释放所有接口。非nil接口在超出范围时会自动释放,在这种情况下是end
之后。您将par
变量设置为nil而不是item
变量。接口是引用计数的,并且XML元素节点具有对其父节点和拥有文档的引用,因此{n} par
变量使item
对象保持活动状态。
我真的希望能够添加到IXMLNodeCollection,然后能够在创建它们之后立即访问代码中的节点。
这就是Add()
返回新添加的节点的原因,因此只需按原样使用它,您就不需要使用Nodes[]
属性来检索它。如果你使用Nodes[]
,你需要很好地理解DOM的布局。