Reputation: 7340
Given the XML sample below;
<role>
<access>
<control>
<type>group</type>
<object>COMPUTER\Administrators</object>
</control>
<control>
<type>user</type>
<object>COMPUTER\Admin</object>
</control>
</access>
</role>
Code:
var
Doc: IXMLDOMDocument2;
Node: IXMLDOMNode;
procedure Test;
begin
Doc := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument2;
Doc.load('test.xml');
// This Works
Node := Doc.selectSingleNode('//role/access/control');
// But this does not work:
Node := Doc.selectSingleNode('//role/access/control[type = ''group'']');
// EDIT: This does work, but how to combine with object=COMPUTER\Admin?
Node := Doc.selectSingleNode('//role/access/control[type="group"]');
// EDIT: This does not work either
Node := Doc.selectSingleNode('//role/access/control[type="group" and object="COMPUTER\Administrators"]');
end;
Upvotes: 4
Views: 5053
Reputation: 15334
Either of these will fix the query:
1) Add the following line after creating the dom:
Doc.setProperty('SelectionLanguage', 'XPath');
2) Better yet, you could be more explicit about which version of the parser you are creating and replace your construction line with this:
Doc := CoDOMDocument60.Create;
If the query doesn't find anything, Node will be empty.
if not Assigned(Node) then...
The default query language for the MSXML3 parser is XSLPatterns. You needed to explicitly set it to XPath. It's been a while since I've had to deal with it, but I assume the CreateOleObject line must create the MSXML parser my default.
Update: Solution for the second half of your question stolen shamelessly (with permission) from the gracious TLama. :)
Ignoring target document formatting and error handling e.g. this way:
procedure TForm1.Button2Click(Sender: TObject);
var
XMLRoot: IXMLDOMNode;
XMLChild: IXMLDOMNode;
XMLDocument: IXMLDOMDocument2;
begin
XMLDocument := CreateOleObject('Microsoft.XMLDOM') as IXMLDomDocument2;
XMLDocument.load('XMLFile.xml');
XMLRoot := XMLDocument.selectSingleNode('//role/access');
if Assigned(XMLRoot) then
begin
XMLRoot := XMLRoot.appendChild(XMLDocument.createElement('control'));
XMLChild := XMLRoot.appendChild(XMLDocument.createElement('type'));
XMLChild.text := 'user';
XMLChild := XMLRoot.appendChild(XMLDocument.createElement('object'));
XMLChild.text := 'COMPUTER\TLama';
XMLDocument.save('XMLFile.xml');
end;
end;
Upvotes: 4
Reputation: 12729
This answer summarizes my entry on the Australian Delphi User's Group blog, "Dances with XML". Refer to it if you need further detail.
You are headed in the right direction, by trying to leverage XPATH as an easy mechanism to access and navigate through an XML document. It just that your implementation needs a bit of polishing. Demonstrative code is shown below.
Use the 'in' operator with an XPATH expression, and the referenced "Dances with XML" utility unit. For example with your supplied input document, this code fragment tests if a control node exists with a
if 'role/access/control[type="group"]' in XFocus(Root) then
ShowMessage(' Hello! I''m here.')
...where Root is the document root node.
For adding stuff, an XML library with a fluent API would be best, but you can achieve semi-fluency with the following methods:
To add a child element use code like this...
ParentNode.AddChild('child-name')
This is semi-fluent because this above expression is a function which returns IXMLNode.
To add a new attribute, or change an existing one use code like this...
ElementNode.Attributes['myattrib'] := 'attrib-value'
There is no native idempotent version of this functionality, but it would be trivial to roll your own.
This example roughly replicates the functionality of the OP's Test() procedure given in the question.
// uses uXMLUtils from referenced demo.
procedure Test;
begin
Doc := LoadDocument_MSXML_FromStream( TestXMLStream);
Root := Doc.Node;
// To test if control node exists:
if 'role/access/control' in XFocus(Root) then
ShowMessage('The control node exists!');
// To access each control node:
for ControlNode in 'role/access/control' then
DoSomethingForEachControlNode( ControlNode);
// To access on the first control node:
for ControlNode in '(role/access/control)[1]' then
DoSomethingForFirstControlNode( ControlNode);
// To access on the first control node which has BOTH group type and Admin object:
for ControlNode in '(role/access/control[type="group"][object="COMPUTER\Administrators"])[1]' do
DoSomething( ControlNode);
// To do something for EACH control node which is EITHER group type or Admin object:
for ControlNode in 'role/access/control[type="group" or object="COMPUTER\Administrators"]' do
DoSomething( ControlNode);
end;
Let's say we want to add a computer administrators group, but only if one does not already exist. If adding, the new nodes go under a new access node. We can achieve this with a trivial amount of code if we leverage XPATH. This is shown in the code fragment below.
if not 'role/access/control[type[.="group"][object[.="COMPUTER\Administrators"]]' in XFocus(Root) then
begin
ControlNode := Root.ChildNodes.FindNode('role')
.AddChild(['access')
.AddChild('control');
ControlNode.AddChild('type' ).Text := 'group';
ControlNode.AddChild('object').Text := 'COMPUTER\Administrators'
end;
Upvotes: 2