Поиск данных в XML с использованием IXMLDocument

Учитывая образец XML ниже;

  1. Как я могу легко проверить, существует ли данный объект?
  2. Как я могу легко добавить элемент типа группы или пользователя? (добавить целый блок)

<role>
    <access>
        <control>
            <type>group</type>
            <object>COMPUTER\Administrators</object>
        </control>
        <control>
            <type>user</type>
            <object>COMPUTER\Admin</object>
        </control>
    </access>
</role>

Код:

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;

2 ответа

Решение

1. Как исправить выражение XPath?

Любой из них исправит запрос:

1) Добавьте следующую строку после создания домена:

  Doc.setProperty('SelectionLanguage', 'XPath');

2) Еще лучше, вы могли бы быть более точным о том, какую версию парсера вы создаете, и замените вашу строку конструктора следующим:

Doc := CoDOMDocument60.Create; 

Если запрос ничего не найдет, узел будет пустым.

if not Assigned(Node) then...

Язык запросов по умолчанию для синтаксического анализатора MSXML3 - XSLPatterns. Вы должны были явно установить его на XPath. Прошло много времени с тех пор, как мне пришлось с этим справиться, но я предполагаю, что строка CreateOleObject должна создать парсер MSXML по умолчанию.

Обновление: Решение для второй половины вашего вопроса, украденное беззастенчиво (с разрешения) из любезного TLama.:)

2. Как добавить "контрольный" узел?

Игнорирование форматирования целевого документа и обработка ошибок, например, так:

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;

Этот ответ резюмирует мою запись в блоге австралийской группы пользователей Delphi "Танцы с XML". Обратитесь к нему, если вам нужна дополнительная информация.

Доступ к узлам через XML

Вы движетесь в правильном направлении, пытаясь использовать XPATH как простой механизм для доступа к документу XML и навигации по нему. Просто ваша реализация нуждается в некоторой полировке. Демонстрационный код показан ниже.

Q1 Как я могу легко проверить, существует ли данный объект?

Используйте оператор "in" с выражением XPATH и указанную служебную единицу "Танцы с XML". Например, с вашим входным документом этот фрагмент кода проверяет, существует ли управляющий узел с

if 'role/access/control[type="group"]' in XFocus(Root) then
    ShowMessage(' Hello! I''m here.')

... где Root - корневой узел документа.

Q2 Как я могу легко добавить элемент типа группы или пользователя?

Для добавления материала лучше всего подойдет библиотека XML с свободно распространяемым API, но вы можете добиться полупроблемы с помощью следующих методов:

Добавить дочерний элемент

Чтобы добавить дочерний элемент, используйте код, подобный этому...

ParentNode.AddChild('child-name')

Это полулегко, потому что это выражение является функцией, которая возвращает IXMLNode.

Добавить атрибут

Чтобы добавить новый атрибут или изменить существующий одноразовый код, как этот...

ElementNode.Attributes['myattrib'] := 'attrib-value'

Не существует нативной идемпотентной версии этой функции, но было бы тривиально накатить свою собственную.

Пример 1

Этот пример примерно повторяет функциональность процедуры OP (), приведенной в вопросе.

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

конец;

Пример 2

Допустим, мы хотим добавить группу администраторов компьютера, но только если она еще не существует. При добавлении новые узлы идут под новым узлом доступа. Мы можем добиться этого с помощью тривиального объема кода, если мы используем XPATH. Это показано во фрагменте кода ниже.

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;
Другие вопросы по тегам