简体   繁体   中英

Search data in XML using IXMLDocument

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;

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

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;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM