在 TXMLDocument 中保留空格时出现 Delphi 异常
Posted
技术标签:
【中文标题】在 TXMLDocument 中保留空格时出现 Delphi 异常【英文标题】:Delphi Exception when Preserving Whitespace in TXMLDocument 【发布时间】:2013-05-31 21:05:40 【问题描述】:这是我之前帖子的后续:String to XmlNode Delphi (or how to add an xml fragment to TXMLDocument) 开始一个新问题似乎很合适......
我实质上是将格式良好的 xml sn-ps 添加到现有的 xmldocument 中。先前解决方案中建议的代码一直运行良好 - 直到 - 我将 [poPreserveWhiteSpace] 添加到 TXMLDocument.ParseOptions。
当我删除 [poPreserveWhiteSpace] 时,一切正常,但没有保留空白。它实际上将结束标签放在新行上。
这是目标 TXMLDocument 的代码。
StoredXMLObj := TXMLDocument.Create(self);
StoredXMLObj.Options := [doNodeAutoCreate, doNodeAutoIndent];
StoredXMLObj.ParseOptions := StoredXMLObj.ParseOptions + [poPreserveWhiteSpace];
StoredXMLObj.XML.Assign(StoredXML); //StoredXML is a TStringList with a complete XML Document
StoredXMLObj.Active := TRUE;
我尝试了上述 Options 和 ParseOptions 的不同组合,但我只能通过删除 [poPreserveWhiteSpace] 来使代码正常工作。
触发异常的代码是第二行:
tmpNode := storedXMLObj.DocumentElement.ChildNodes[i]; // <Class> node
tmpNode.ChildNodes.Nodes[1].ChildNodes.Nodes[0].ChildNodes.Add(LoadXMLData(MissingElements[j]).DocumentElement); //TMPNode is an IXMLNode and MissingElements is a TStringList
在添加 xml sn-p 之前,我尝试创建对 LoadXMLData(..) 的返回值的引用,并将这些 ParseOptions 设置为匹配,但那里也没有运气。
有什么想法吗?
编辑:添加自包含的示例代码来演示问题。明确的标题。 这是一些简化的代码。请注意,除非您注释掉包含 [poPreserveWhitespace] 的行,否则会有例外。 **Edit2:根据 Remy 的建议调整代码以保留空格。调用 FormatXMLData 时还是有问题。
procedure TForm2.BitBtn2Click(Sender: TObject);
var
FragmentXMLObj : TXMLDocument;
StoredXMLObj : TXMLDocument;
FragNode : IXMLNode; //THIS SHOULD BE IXMLNODE, RIGHT?
XMLStarting, XMLFragment, XMLMerged : TStringList;
i : integer;
begin
//StringLists to hold xml data
XMLStarting := TStringList.Create; //COMPLETE XML
XMLFragment := TStringList.Create; //XML FRAGMENT TO INSERT INTO COMPLETE XML
XMLMerged := TStringList.Create; //MERGE OF THE ABOVE TWO.
//STARTING XML
XMLStarting.Add('<?xml version="1.0" encoding="UTF-16" standalone="no"?>');
XMLStarting.Add('<Programs>');
XMLStarting.Add(' <Program_Group Batch_No="12345678-1234-1234-1234-123456789ABC" Description="FOO_824_1">');
XMLStarting.Add(' <Program Name="PROG_1">');
XMLStarting.Add(' <Class Name="CLASS_1">');
XMLStarting.Add(' <Property Name="DB" RttiType="tkString"> </Property>');
XMLStarting.Add(' <Property Name="SystemDate" RttiType="tkClass" ClassType="TXSDATE">12/30/1899</Property>');
XMLStarting.Add(' </Class>');
XMLStarting.Add(' </Program>');
XMLStarting.Add(' </Program_Group>');
XMLStarting.Add('</Programs>');
//XML DOCUMENT OBJECT
StoredXMLObj := TXMLDocument.create(self);
//PROBLEM LINE START
StoredXMLObj.ParseOptions := StoredXMLObj.ParseOptions + [poPreserveWhiteSpace];
//PROBLEM LINE END
StoredXMLObj.Options := [doNodeAutoCreate, doNodeAutoIndent];
StoredXMLObj.XML.Text := XMLStarting.Text;
StoredXMLObj.Active := TRUE;
//XML FRAGMENT WITH SPACES
XMLFragment.Add('<ParentNode>');
XMLFragment.Add('<Property Name="VRSN" RttiType="tkString"> </Property>');
XMLFragment.Add('<Property Name="ShowMetaData" RttiType="tkBoolean"> </Property>');
XMLFragment.Add('</ParentNode>');
//--OLD CODE THAT RAISES EXCEPTION--
//INSERTING XML FRAGMENT INTO STARTING XML
// FragNode := storedXMLObj.DocumentElement.ChildNodes[0];
// FragNode.ChildNodes.Nodes[0].ChildNodes.Nodes[0].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement.ChildNodes.Nodes[0]);
// FragNode.ChildNodes.Nodes[0].ChildNodes.Nodes[0].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement.ChildNodes.Nodes[1]);
//--OLD CODE THAT RAISES EXCEPTION--
FragNode := storedXMLObj.DocumentElement.ChildNodes[1];
FragmentXMLObj := TXMLDocument.Create(self);
FragmentXMLObj.ParseOptions := FragmentXMLObj.ParseOptions + [poPreserveWhiteSpace];
FragmentXMLObj.Options := [doNodeAutoCreate, doNodeAutoIndent];
FragmentXMLObj.LoadFromXML(XMLFragment.Text);
//FragNode.ChildNodes.Nodes[1].ChildNodes.Nodes[1].ChildNodes.Add(FragmentXMLObj.DocumentElement); //this also pulls in the parent tags, which I don't want.
for i := 0 to FragmentXMLObj.DocumentElement.ChildNodes.Count-1 do //easier to just pull in all the nodes (including whitespace, then formatxml to cleanup).
FragNode.ChildNodes.Nodes[1].ChildNodes.Nodes[1].ChildNodes.Add(FragmentXMLObj.DocumentElement.ChildNodes.Nodes[i]);
FragmentXMLObj.Free;
XMLMerged.Text := StoredXMLObj.XML.Text;
XMLMerged.Text := FormatXMLData(XMLMerged.Text); //UGH... FormatXMLData WIPES OUT WHITESPACE PROPERTY VALUES!! Doesn't seem to have any settings either...
XMLMerged.SaveToFile('c:\merged.xml');
XMLStarting.Free;
XMLFragment.Free;
XMLMerged.Free;
StoredXMLObj.Free;
end;
生成的合并 XML 文件...空白属性值在格式化期间被清除(我确实需要格式化数据,它真的很难看)。
<?xml version="1.0" encoding="UTF-16" standalone="no"?>
<Programs>
<Program_Group Batch_No="12345678-1234-1234-1234-123456789ABC" Description="FOO_824_1">
<Program Name="PROG_1">
<Class Name="CLASS_1">
<Property Name="DB" RttiType="tkString"/>
<Property Name="SystemDate" RttiType="tkClass" ClassType="TXSDATE">12/30/1899</Property>
<Property Name="VRSN" RttiType="tkString"/>
<Property Name="ShowMetaData" RttiType="tkBoolean"/>
</Class>
</Program>
</Program_Group>
</Programs>
【问题讨论】:
【参考方案1】:LoadXMLData()
期望输入字符串是格式良好的 XML 文档。我为您之前的问题提供的解决方案有效,因为您指定了单独的 XML 元素,这些元素本身可以作为独立文档。但是 PCDATA 元素本身并不是格式良好的 XML 文档。尝试将其包装在假元素中,例如:
tmpDoc := LoadXMLData('<Doc>' + MissingElements[j] + '</Doc>').DocumentElement;
for I := 0 to tmpDoc.ChildNodes.Count-1 do
tmpNode.ChildNodes[1].ChildNodes[0].ChildNodes.Add(tmpDoc.ChildNodes[I]);
更新:您收到“索引超出范围”错误,因为您在访问 ChildNodes
时没有考虑空白 DOM 节点。
鉴于您显示的 XML:
XMLStarting.Add('<?xml version="1.0" encoding="UTF-16" standalone="no"?>');
XMLStarting.Add('<Programs>');
XMLStarting.Add(' <Program_Group Batch_No="12345678-1234-1234-1234-123456789ABC" Description="FOO_824_1">');
XMLStarting.Add(' <Program Name="PROG_1">');
XMLStarting.Add(' <Class Name="CLASS_1">');
XMLStarting.Add(' <Property Name="DB" RttiType="tkString"> </Property>');
XMLStarting.Add(' <Property Name="SystemDate" RttiType="tkClass" ClassType="TXSDATE">12/30/1899</Property>');
XMLStarting.Add(' </Class>');
XMLStarting.Add(' </Program>');
XMLStarting.Add(' </Program_Group>');
XMLStarting.Add('</Programs>');
鉴于您显示的代码失败:
FragNode := storedXMLObj.DocumentElement.ChildNodes[0];
FragNode.ChildNodes.Nodes[0].ChildNodes.Nodes[0].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement.ChildNodes.Nodes[0]);
以下是正确的:
storedXMLObj.DocumentElement
指的是 <Programs>
节点。
它的ChildNodes[0]
节点指的是<Programs>
和<Program_Group>
节点之间的空格,但您希望它改为引用<Program_Group>
节点。
因此,FragNode.ChildNodes.Nodes[0]
失败,因为 FragNode 是一个没有子节点的纯文本节点!
您可以自己确认。 FragNode.NodeName
是 '#text'
,FragNode.NodeType
是 ntText
,FragNode.NodeValue
是 #$A' '
,FragNode.HasChildNodes
是 False,FragNode.IsTextElement
是 True。
换句话说,上面的 XML 具有以下结构:
ntElement 'Programs'
|
|_ ntText #$A' '
|
|_ ntElement 'Program_Group'
|
|_ ntText #$A' '
|
|_ ntElement 'Program'
| |
| |_ ntText #$A' '
| |
| |_ ntElement 'Class'
| | |
| | |_ ntText #$A' '
| | |
| | |_ nElement 'Property'
| | | |
| | | |_ ntText ' '
| | |
| | |_ ntText #$A' '
| | |
| | |_ ntElement 'Property'
| | | |
| | | |_ ntText '12/30/1899'
| | |
| | |_ ntText #$A' '
| |
| |_ ntText #$A' '
|
|_ ntText #$A' '
希望这能让它更清楚一点。
所以,要完成你想要做的事情,你需要更多这样的东西:
FragNode := storedXMLObj.DocumentElement.ChildNodes[1];
FragNode.ChildNodes.Nodes[1].ChildNodes.Nodes[1].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement);
FragNode.ChildNodes.Nodes[1].ChildNodes.Nodes[1].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement);
如果您想在 LoadXMLData()
片段中保留空白,则必须直接使用 TXMLDocument,因为 LoadXMLData()
不允许您设置 poPreserveWhiteSpace
标志:
FragmentXMLObj := TXMLDocument.Create(self);
FragmentXMLObj.ParseOptions := FragmentXMLObj.ParseOptions + [poPreserveWhiteSpace];
FragmentXMLObj.Options := [doNodeAutoCreate, doNodeAutoIndent];
FragmentXMLObj.LoadFromXML(XMLFragment.Text);
FragNode.ChildNodes.Nodes[1].ChildNodes.Nodes[1].ChildNodes.Add(FragmentXMLObj.DocumentElement);
FragmentXMLObj.Free;
为避免ChildNodes
索引出现任何问题,您最好使用XPath 查询,这样您就可以让DOM 搜索您想要插入片段的<Class>
节点。
无论哪种方式,您很快就会发现这不会生成看起来非常漂亮的 XML。如果您只是希望存在空白,但实际上不需要按原样保留原始空白,那么您最好禁用 poPreserveWhiteSpace
标志,然后在保存最终时使用 FormatXMLData()
文件:
XMLMerged.Text := FormatXMLData(StoredXMLObj.XML.Text);
【讨论】:
我添加的元素与之前的格式完全相同。我不清楚为什么添加空白保留会改变 TXMLDocument 的行为。 我使用与以前相同的元素测试了代码,无论是否保留空白,它们都被很好地添加了。 就像我说的,当我尝试在目标中添加启用了空白保留的新节点时,它对我来说效果很好。所以其他事情正在发生。 我毫不怀疑这是真的。我会继续挖掘并分享我的发现。 好吧,我的挖掘工作收效甚微。我更新了我的帖子以包含一些自包含的示例代码。打开 [poPreserveWhitespace] 时仍然会出现运行时异常。有没有人得到同样的结果?以上是关于在 TXMLDocument 中保留空格时出现 Delphi 异常的主要内容,如果未能解决你的问题,请参考以下文章
使用 BeautifulSoup 写入文件时在 Django 模板中保留空格
Spring-Boot Config:如何在用于填充 Map<String, String> 的 yaml 键中保留空格
通过 ruamel.yaml 转储时如何在 yaml 文件中保留空值