3
votes

Having hard time with IDOMNode and IXMLNode distinction. I want to append a child element in a document to a node selected with XPath.

What I tried:

Effort 1:

I get an XPath result node N:IDOMNode from IDOMNodeSelect.selectNodes(expression); If I convert it back to IXMLNode using

intfDocAccess : IXmlDocumentAccess;
doc: TXMLDocument;
...
if Supports( N.OwnerDocument, IXmlDocumentAccess, intfDocAccess) then
    doc := intfDocAccess.DocumentObject
  else
    doc := nil;
  NodeAsIXMLNode := TXmlNode.Create( N, nil, doc );

...then adding a child to it throws access violation. Probably NodeAsIXMLNode is not even in the original document, it's just a copy created for type compatibility...

Effort 2:

Try to add a child node to the XPath result node directly:

XMLNode := XmlDoc.CreateElement( 'tag', '' );
N.appendChild( XMLNode as IDOMNode );

It throws Interface not supported. I have a feeling that the xpath result IDOMNode node is also not a member of the original IXMLDocument, just some result copy. Just a guess.

So how could I select a node using xpath, then append a child element node to it? So my original IXMLDocument gets updated.

Update: Traversing the whole xml document tree and comparing the IXMLNode's DOMNode with the XPath result DOMNode also not working - XPath result nodes are not contained in the original doc, it turns out. Tried msxml, adom and omnixml implementations /XE7/

Update 2: Managed to work with the first method, just replacing

doc := nil;

with

doc := _xpathdoc as TXMLDocument; // _xpathdoc : the IXMLDocument

in the converter function.

1
XPath is meant for searching only, not for manipulating the document. You can't translate an XPath IDOMNode back into the original IXMLNode, and selectNodes() does not report where DOM nodes were found within the document. Iterating the document looking for matching nodes defeats the purpose of using XPath, you may as well just iterate the document manually in the first place and not use XPath at all.Remy Lebeau
Thanks, maybe, but why not use xpath like that when it's so comfortable? Of course I could manipulate the document in a way that renders xpath results invalid meanwhile - but I don't. See Update 2 for a currently working way.kgz
Note that doc := TXMLDocument(_xpathdoc); is considered an "unsafe" typecast and will return nil if the cast fails. A "safe" cast is to use the as operator instead so it raises an exception if the cast fails: doc := _xpathdoc as TXMLDocument;. See Casting Interface References to Objects on Embarcadero's DocWiki.Remy Lebeau
Thanks, updated the code above according to your recommendation.kgz

1 Answers

0
votes

I'm not sure if this helps, but suppose you have an XML document

<Content>
  <Clients>
    <Client>
      <ID value="88"/>
      <Forename value="John"/>
      <Surname value="Smith"/>
    </Client>
  </Clients>
</Content>

loaded into a TMemo and a TEdit, edPathQuery, containing /Content/Clients. Then, the following code will find the Clients node and append a new node + value to it

procedure TForm1.btnLoadClick(Sender: TObject);
var
  XmlDoc: IXMLDOMDocument;
  NodeList : IXmlDOMNodeList;
  Node,
  NewNode : IXmlDomNode;
  E : IXmlDomElement;
  TextNode : IXMLDOMText;
  Value : String;
  I : Integer;
begin
  Memo2.Lines.Clear;
  XmlDoc := CoDOMDocument.Create; //CreateOleObject('Microsoft.XMLDOM') as IXMLDOMDocument;
  XmlDoc.Async := False;
  XmlDoc.LoadXML(Memo1.Lines.Text);
  if xmlDoc.parseError.errorCode <> 0 then
    raise Exception.Create('XML Load error:' + xmlDoc.parseError.reason);

  NodeList := XmlDoc.documentElement.SelectNodes(edPathQuery.Text);

  if NodeList.length > 0 then begin
    E := XMLDoc.createElement('Added');
    //  E.nodeValue := 'Something'; Note the error this raises.  Using the
    //  TextNode as below avoids this
    NewNode := E as IXMLDomNode;
    TextNode := XMLDoc.createTextNode('Data');
    NewNode.appendChild(TextNode);
    NodeList.item[0].appendChild(NewNode);
  end;
  Memo2.Lines.Text := XMLDoc.documentElement.xml;
end;

This code uses the MSXML.Pas import unit that comes with D7, and was generated with the version of MSXML.DLL which was current at the time (C:\WINDOWS\SYSTEM\MSXML.DLL).

Personally, I have never found Delphi's "abstracted" TXmlDocument helpful - I find it easier to work with MSXML's objects instead. Ymmv ...