Практика
IXMLDocument - пустые пространства имен xmlns

:: Меню ::
:: На главную ::
:: FAQ ::
:: Заметки ::
:: Практика ::
:: Win API ::
:: Проекты ::
:: Скачать ::
:: Секреты ::
:: Ссылки ::

:: Сервис ::
:: Написать ::

:: MVP ::

:: RSS ::

Яндекс.Метрика


С неприятной ситуацией в IXMLDocument можно столкнуться, работая с пространством имен – xmlns. А неприятная она своим недетерминированным поведением, ведь поддерживать код, который на разный машинах выдает разный результат, практически невозможно. Рассмотрим ситуацию на простом xml следующего вида:



    child1
    child2


Для создания такого XML напишем небольшой код:

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := TXMLDocument.Create(nil);

  Document.Active := True;
  Document.Version := '1.0';
  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Root := Document.AddChild('root');

  Node1 := Root.AddChild('title1');
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2');
  Node2.Text := 'child2';

  ShowMessage(Document.Node.XML);
end;

Этот код можно немного упростить:

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := NewXMLDocument;

  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Root := Document.AddChild('root');

  Node1 := Root.AddChild('title1');
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2');
  Node2.Text := 'child2';

  ShowMessage(Document.Node.XML);
end;

На этом этапе нет никаких проблем, так как пространство имен еще не определено (вернее оно пустое по умолчанию). Но если теперь мы добавим пространство имен корневому узлу

procedure TForm1.Button1Click(Sender: TObject);
{...}
begin
  {...}
  Root := Document.AddChild('root');
  Root.Attributes['xmlns'] := 'http://www.w3.org/2005/Atom';
  {...}
end;

Увидим, что в узлах title{...} появился пустой атрибут xmlns



    child1
    child2


Вероятной причиной такого поведения является то, что newNode (title) исходит из документа без объявленного пространства имен, но oldNode (root) находится в документе с пространством имен. В этой ситуации узел переносит свое пустое пространство имен в документ, и оно отображается явно.

Приняв это во внимание, попробуем решить проблему, для этого попробуем добавить пространство имен ко всем узлам, порожденным от корневого.

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := NewXMLDocument;

  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Root := Document.AddChild('root');
  Root.Attributes['xmlns'] := 'http://www.w3.org/2005/Atom';

  Node1 := Root.AddChild('title1', Root.NamespaceURI);
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2', Root.NamespaceURI);
  Node2.Text := 'child2';

  ShowMessage(Document.Node.XML);
end;

Но это не помогает. Причина этого становится понятна если, посмотреть на значение Root.NamespaceURI под отладчиком (или вывести его в ShowMessage) – оно пустое. Сделаем вывод – пространство имен невозможно определить путем установки атрибута с именем xmlns. Зададим пространство имен при создании дочерних узлов явно, через DeclareNamespace:

procedure TForm1.Button1Click(Sender: TObject);
{...}
begin
  {...}

  Root := Document.AddChild('root');
  Root.DeclareNamespace('', 'http://www.w3.org/2005/Atom');

  Node1 := Root.AddChild('title1', Root.NamespaceURI);
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2', Root.NamespaceURI);
  Node2.Text := 'child2';

  {...}
end;

И снова ничего не изменилось, а это уже выглядит странно, ведь пространство имен для Root указано явно вызовом метода DeclareNamespace. Для того чтобы код заработал в таком виде, пространство имен у дочерних узлов нужно указывать явно (в виде строки), что на мой взгляд не совсем правильно.

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := NewXMLDocument;

  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Root := Document.AddChild('root');
  Root.DeclareNamespace('', 'http://www.w3.org/2005/Atom');

  Node1 := Root.AddChild('title1', 'http://www.w3.org/2005/Atom');
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2', 'http://www.w3.org/2005/Atom');
  Node2.Text := 'child2';

  ShowMessage(Document.Node.XML);
end;

Теперь мы получим ожидаемый результат



    child1
    child2


Проблему с установкой namespace решает создание корневого узла с помощью вызова метода GetDocBinding (в этом случае значение параметра NamespaceURI будет определено):

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := NewXMLDocument;
  Root := Document.GetDocBinding('root', TXMLNode, 'http://www.w3.org/2005/Atom') as IXMLNode;

  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Node1 := Root.AddChild('title1');
  Node1.Text := 'child1';

  Node2 := Root.AddChild('title2');
  Node2.Text := 'child2';

  ShowMessage(Document.Node.XML);
end;

Используя метод GetDocBinding можно создавать дочерние узлы от самого документа, а не от корневого узла, результат будет тем, который нам нужен:

procedure TForm1.Button1Click(Sender: TObject);
var
  Document: IXMLDocument;
  Root, Node1, Node2: IXMLNode;
begin
  Document := NewXMLDocument;

  Document.Encoding := 'UTF-8';
  Document.NodeIndentStr := '    ';
  Document.Options := Document.Options + [doNodeAutoIndent];

  Root := Document.GetDocBinding('root', TXMLNode, 'http://www.w3.org/2005/Atom') as IXMLNode;

  Node1 := Document.CreateElement('title1', Root.NamespaceURI);
  Node1.Text := 'child1';
  Document.DocumentElement.ChildNodes.Add(Node1);

  Node2 := Document.CreateElement('title2', Root.NamespaceURI);
  Node2.Text := 'child2';
  Document.DocumentElement.ChildNodes.Add(Node2);

  ShowMessage(Document.Node.XML);
end;

Проблема с xmlns решена, а использование GetDocBinding кажется единственно правильным решением для генерации корректного xml.

При использовании материала - ссылка на сайт обязательна