4

Given the XML sample below;

  1. How can I easily check if a given object exists?
  2. How can I easily add an item of type group or user? (add a whole block)

<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;
Remko
  • 7,214
  • 2
  • 32
  • 52
  • @Adam: +1 good spot, that is my error so I replaced the screenshot with a correct one. – Remko Aug 28 '12 at 19:58
  • Why do you guys post screenshots of text files? Just curious... – Adam Aug 28 '12 at 20:08
  • @Adam, because they shouldn't be. :-) The XML should be posted as text content (and it's not hard to format properly, either) so that people who want to check their answers before posting them can copy and paste it. – Ken White Aug 28 '12 at 20:12
  • @Adam for some reason SO doesn't display it correctly even though I mark it as code – Remko Aug 28 '12 at 20:13
  • 1
    What do you mean with *add an item of type group or user* ? Do you mean, how to add a whole `` node or just add a single `` to an existing `` ? – TLama Aug 28 '12 at 21:20
  • @TLama I meant add a whole node – Remko Aug 28 '12 at 21:21

2 Answers2

4

1. How to fix the XPath expression ?

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. :)

2. How to add a "control" node ?

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;
TLama
  • 75,147
  • 17
  • 214
  • 392
Bruce McGee
  • 15,076
  • 6
  • 55
  • 70
2

This answer summarizes my entry on the Australian Delphi User's Group blog, "Dances with XML". Refer to it if you need further detail.

Access to nodes via XML

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.

Q1 How can I easily check if a given object exists?

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.

Q2 How can I easily add an item of type group or user?

For adding stuff, an XML library with a fluent API would be best, but you can achieve semi-fluency with the following methods:

Add a child element

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.

Add an attribute

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.

Example 1

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;

Example 2

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;
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
  • +1, But I prefer to do this without using extra unit/3rd party stuff so I accepted the other answer. – Remko Aug 29 '12 at 19:31