Frage

This is a follow up to my earlier post: String to XmlNode Delphi (or how to add an xml fragment to TXMLDocument) It seemed appropriate to start a new question...

I am essentially adding well formed xml snippets to an existing xmldocument. The code suggested in the previous solution had been working great - until - I added [poPreserveWhiteSpace] to TXMLDocument.ParseOptions.

When I remove [poPreserveWhiteSpace] everything works fine, but whitespace is not preserved. It actually puts the closing tag on a new line.

Here is a code snippet ot the Target 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;

I have tried different combinations of the Options and ParseOptions above, but I can only get the code to work by removing [poPreserveWhiteSpace].

The code that triggers the exception is the second line of:

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

I tried creating a reference to the return value of LoadXMLData(..), and setting those ParseOptions to match, before adding the xml snippet, but no luck there either.

Any thoughts?

Edit: Adding self contained sample code to demonstrate problem. Clarified Title. Here is some simplified code. Note that there will be an exception unless you comment out the line containing [poPreserveWhitespace]. **Edit2: Tweaking code to preserve whitespace as per Remy's suggestion. Still has problem when calling 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;

The Resulting Merged XML File... Whitespace property values got wiped out during formatting (and I do need to format the data, otw it is REALLY ugly).

<?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>
War es hilfreich?

Lösung

LoadXMLData() expects the input string to be a well-formed XML document. The solution I gave you for your previous question worked because you were specifying individual XML elements which by themselves can act be standalone documents. But a PCDATA element by itself is not a well-formed XML document. Try wrapping it in a fake element, eg:

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]);

Update: You are getting an "index out of bounds" error because you are not taking the whitespace DOM nodes into account when accessing the ChildNodes.

Given the XML you have shown:

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>');

And given the code you have shown which is failing:

FragNode := storedXMLObj.DocumentElement.ChildNodes[0];
FragNode.ChildNodes.Nodes[0].ChildNodes.Nodes[0].ChildNodes.Add(LoadXMLData(XMLFragment.Text).DocumentElement.ChildNodes.Nodes[0]);

The following is true:

  1. storedXMLObj.DocumentElement refers to the <Programs> node.
  2. its ChildNodes[0] node refers to the whitespace between the <Programs> and <Program_Group> nodes, but you are expecting it to refer to the <Program_Group> node instead.
  3. thus, FragNode.ChildNodes.Nodes[0] fails because FragNode is a text-only node that has no children!

You can confirm that for yourself. FragNode.NodeName is '#text', FragNode.NodeType is ntText, FragNode.NodeValue is #$A' ', FragNode.HasChildNodes is False, and FragNode.IsTextElement is True.

In other words, the above XML has the following structure to it:

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' '

Hopefully that makes it a bit clearer.

So, to accomplish what you are attempting to do, you would need something more like this:

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);

If you want to preserve whitespace in the LoadXMLData() fragments, you will have to use TXMLDocument directly instead since LoadXMLData() does not let you set the poPreserveWhiteSpace flag:

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;

To avoid any problem with ChildNodes indexes, you are better off using an XPath query instead, so you can let the DOM search for the <Class> node that you want to insert fragments into.

Either way, you will soon discover that this does not produce very nice looking XML. If you just want there to be whitespace present, but you don't actually need to preserve the original whitespace as-is, then you are better off disabling the poPreserveWhiteSpace flag, and then use FormatXMLData() when you are saving the final document:

XMLMerged.Text := FormatXMLData(StoredXMLObj.XML.Text);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top