0

这是我之前帖子的后续:String to XmlNode Delphi (or how to add an x​​ml fragment to TXMLDocument) 开始一个新问题似乎很合适......

我本质上是将格式良好的 xml 片段添加到现有的 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 片段之前,我尝试创建对 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>
4

1 回答 1

2

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.

鉴于您显示的 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]);

以下情况属实:

  1. storedXMLObj.DocumentElement<Programs>节点。
  2. 它的ChildNodes[0]节点指的是节点之间的空白<Programs><Program_Group>,但您希望它指的是<Program_Group>节点。
  3. 因此,FragNode.ChildNodes.Nodes[0]失败是因为FragNode 是一个没有子节点的纯文本节点!

你可以自己确认。 FragNode.NodeName'#text'FragNode.NodeTypentTextFragNode.NodeValue#$A' 'FragNode.HasChildNodes是假,FragNode.IsTextElement是真。

换句话说,上面的 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);
于 2013-05-31T21:57:05.467 回答