XML Tutorial/ru

From Lazarus wiki
Revision as of 16:04, 28 May 2011 by Eastorwest (talk | contribs) (Генерация файла XML)

Deutsch (de) English (en) español (es) français (fr) magyar (hu) Bahasa Indonesia (id) italiano (it) 日本語 (ja) 한국어 (ko) português (pt) русский (ru) 中文(中国大陆)‎ (zh_CN)

Введение

XML - Расширяемый Язык Разметки (eXtensible Markup Language) рекомендован W3C как язык для обмена информацией между различными системами. Это ориентированный на текст способ сохранения информации. Современные языки обмена данными, такие как XHTML, так же как и большинство технологий WebServices, основаны на XML.

В настоящее время в Free Pascal существует ряд модулей для поддержки XML. Эти модули называются "XMLRead", "XMLWrite" и "DOM", и являются частью Free Component Library (FCL) из комплекта Free Pascal. FCL уже находится в заданном по умолчанию пути поиска файлов для компилятора в Лазарусе, таким образом Вам нужно только добавить названия модулей в строку USES чтобы получить поддержку XML в Вашей программе. Использование XML пока не документировано, поэтому данная статья даёт необходимые вводные сведения для работы с модулями поддержки XML.

DOM XML (Объектная модель документов) - это ряд стандартизированных объектов, которые предоставляют однотипный интерфейс для использования XML в различных языках и платформах. Стандарт определяет только методы, свойства и другие части интерфейса объекта, оставляя реализацию свободной для различных языков. FCL в настоящее время поддерживает полностью DOM 1.0.

Примеры

В статье даны примеры работы с XML-данными по принципу нарастающей сложности.

Чтение текстового узла

Для Delphi-программистов:

Помните, что когда Вы работаете с TXMLDocument, текст в пределах узла считается отдельным текстовым узлом. Таким образом, Вы должны обратиться к текстовому значению узла, как к отдельному узлу. Альтернативно, свойство TextContent может быть использовано для возврата значения всех узлов лежащих ниже, которые связаны с данным.

Процедура ReadXMLFile всегда создаёт новый TXMLDocument, таким образом Вы не должны создавать его заранее. Однако Вы должны вызывать метод Free вручную после окончания работы с документом для освобождения ресурсов занятых объектом TXMLDocument.

Для примера рассмотрим следующий XML-файл:

<xml>

<?xml version="1.0"?>
<request>
  <request_type>PUT_FILE</request_type>
  <username>123</username>
  <password>abc</password>
</request>

</xml>

Следующий пример показывает корректный и некорректный способы получения значений текстового узла xml:

<delphi>

var
 PassNode: TDOMNode;
 Doc:      TXMLDocument;
begin
 // Читаем xml файл с жесткого диска
 ReadXMLFile(Doc, 'c:\xmlfiles\test.xml');
 // Запрашиваем узел с именем "password"
 PassNode := Doc.DocumentElement.FindNode('password');
 // Выводим значение выбранного узла 
 //Неправильный способ
 WriteLn(PassNode.NodeValue); // вывод будет пустым
 //Правильный способ
 // Текст узла - это отдельный дочерний узел
 WriteLn(PassNode.FirstChild.NodeValue); // правильно выведет "abc"
 // Альтернативный способ
 WriteLn(PassNode.TextContent);
 // В завершении делаем Free для документа
 Doc.Free;

end; </delphi>

Вывод названий узлов

Маленькое замечание о навигации по дереву DOM:

Для последовательного доступа к узлам лучше всего использовать свойства FirstChild и NextSibling (чтобы шагать вперед по дереву) или LastChild и PreviousSibling (назад с конца дерева). Для произвольного доступа к узлам дерева можно пользоваться свойством ChildNodes или методом GetElementsByTagName, но эти методы создают объект TDOMNodeList, который после использования должен быть освобождён. Это поведение отличается от других реализаций DOM (например, MSXML), поскольку FCL реализация основана на объектах, а не на интерфейсах.

Следующий пример демонстрирует, как выводить имена узлов в компонент TMemo, расположенный на форме.

Ниже приведён XML-файл с именем 'C:\Programs\test.xml':

<xml>

<?xml version="1.0"?>
<images directory="mydir">
 <imageNode URL="graphic.jpg" title="">
   <Peca DestinoX="0" DestinoY="0">Pecacastelo.jpg1.swf</Peca>
   <Peca DestinoX="0" DestinoY="86">Pecacastelo.jpg2.swf</Peca>
 </imageNode>
</images>

</xml>

И код на Pascal, который выполняет эту задачу:

<delphi>

var
  Documento: TXMLDocument;
  Child: TDOMNode;
  j: Integer;
begin
  ReadXMLFile(Documento, 'C:\Programas\teste.xml');
  Memo.Lines.Clear;
  // Используем свойства FirstChild и NextSibling
  Child := Documento.DocumentElement.FirstChild;
  while Assigned(Child) do
  begin
    Memo.Lines.Add(Child.NodeName + ' ' + Child.Attributes.Item[0].NodeValue);
    // Используем свойство ChildNodes
    with Child.ChildNodes do
    try
      for j := 0 to (Count - 1) do
        Memo.Lines.Add(Item[j].NodeName + ' ' + Item[j].FirstChild.NodeValue);
    finally
      Free;
    end;
    Child := Child.NextSibling;
  end;
  Documento.Free;
end;

</delphi>

В результате программа выведет следующее:

imageNode graphic.jpg
Peca Pecacastelo.jpg1.swf
Peca Pecacastelo.jpg1.swf

Загрузка XML в TreeView

Одно из обычных использований файла XML - разбор и показ информации в древовидном формате. Вы можете отыскать компонент TTreeView на вкладке "Common Controls" Lazarus'а.

Функция, приведённая ниже, возмёт документ XML, предварительно загруженный из файла или сгенерированный программно, и заполнит TreeView его содержимым. Заголовком каждого узла будет содержимое первого атрибута этого узла.

<delphi> procedure TForm1.XML2Tree(tree: TTreeView; XMLDoc: TXMLDocument); var

 iNode: TDOMNode;
 procedure ProcessNode(Node: TDOMNode; TreeNode: TTreeNode);
 var
   cNode: TDOMNode;
   s: string;
 begin
   if Node = nil then Exit; // выходим, если достигнут конец документа
   
   // добавляем узел в дерево
   if Node.HasAttributes and (Node.Attributes.Length>0) then
     s:=Node.Attributes[0].NodeValue
   else
     s:=; 
   TreeNode := tree.Items.AddChild(TreeNode, s);
   // переходим к дочернему узлу
   cNode := Node.FirstChild;
   // проходим по всем дочерним узлам
   while cNode <> nil do
   begin
     ProcessNode(cNode, TreeNode);
     cNode := cNode.NextSibling;
   end;
 end;
   

begin

 iNode := XMLDoc.DocumentElement.FirstChild;
 while iNode <> nil do
 begin
   ProcessNode(iNode, nil); // Рекурсия
   iNode := iNode.NextSibling;
 end;

end; </delphi>

Изменение XML документа

Первая вещь, о которой следует помнить, TDOMDocument это хэндл (handle) к DOM. Вы можете получить экземпляр этого класса создавая или загружая XML документ.

С другой стороны, узлы не могут быть созданы как обычные объекты. Вы должны использовать методы, которые предоставляет TDOMDocument для их создания и, впоследствии, использовать другие методы для помещения их в нужное место дерева. Это говорит о том, что узлы должны принадлежать вполне определённому документу DOM.

Вот некоторые общераспространённые методы TDOMDocument:

<delphi>

  function CreateElement(const tagName: DOMString): TDOMElement; virtual;
  function CreateTextNode(const data: DOMString): TDOMText;
  function CreateCDATASection(const data: DOMString): TDOMCDATASection;
    virtual;
  function CreateAttribute(const name: DOMString): TDOMAttr; virtual;

</delphi>

Вот, в качестве примера, метод, который ищет выбранный элемент в TTreeView и затем вставляет дочерний узел в документ XML, где он должен находиться . TreeView должен быть предварительно заполнен содержанием из XML файла, используя XML2Tree function.

<delphi> procedure TForm1.actAddChildNode(Sender: TObject); var

 position: Integer;
 NovoNo: TDomNode;

begin

 {*******************************************************************
 *  Определение выбраного элемента
 *******************************************************************}
 if TreeView1.Selected = nil then Exit;
 if TreeView1.Selected.Level = 0 then
 begin
   position := TreeView1.Selected.Index;
   NovoNo := XMLDoc.CreateElement('item');
   TDOMElement(NovoNo).SetAttribute('nome', 'Item');
   TDOMElement(NovoNo).SetAttribute('arquivo', 'Arquivo');
   with XMLDoc.DocumentElement.ChildNodes do
   begin
     Item[position].AppendChild(NovoNo);
     Free;
   end;
   {*******************************************************************
   *  Обновление TreeView
   *******************************************************************}
   TreeView1.Items.Clear;
   XML2Tree(TreeView1, XMLDoc);
 end
 else if TreeView1.Selected.Level >= 1 then
 begin
   {*******************************************************************
   *  Эта функция работает только на верхнем уровне дерева
   *  и вы должны её модефицировать, чтобы использовать другие уровни
   *******************************************************************}
 end;

end; </delphi>

Создание TXMLDocument из строки

Если данные XML находятся в строке MyXmlString, создать из нее DOM можно с помощью следующего кода:

<delphi> Var

 S : TStringStream;
 XML : TXMLDocument;

begin

 S:= TStringStream.Create(MyXMLString);
 Try
   S.Position:=0;
   XML:=Nil;
   ReadXMLFile(XML,S); // XML документ целиком
   // Альтернативно:
   ReadXMLFragment(AParentNode,S); // Читаем только XML фрагмент.
 Finally
   S.Free;
 end;

end; </delphi>

Проверка достоверности документа

Начиная с марта 2007, в FCL XML парсер добавлена возможность проверки достоверности (валидации) документа с помощью DTD. Проверка достоверности определяет соответствие логической структуры документа определенным правилам, заданных с помощью "Определение Типа документа" (DTD).

Это пример XML-документа, содержащего DTD:

<xml>

 <?xml version='1.0'?>
 <!DOCTYPE root [
 <!ELEMENT root (child)+ >
 <!ELEMENT child (#PCDATA)>
 ]>
 <root>
   <child>This is a first child.</child>
   <child>And this is the second one.</child>
 </root>

</xml>

Этот DTD определяет, что элемент root должен содержать один или более элемент child, а элементы child могут содержать только символьные данные. Если парсер обнаружит нарушение этих правил, то он сообщит о них.

Загрузка такого документа несколько сложнее. Пусть исходные XML данные содержатся в потоке AStream:

<delphi> procedure TMyObject.DOMFromStream(AStream: TStream); var

 Parser: TDOMParser;
 Src: TXMLInputSource;
 TheDoc: TXMLDocument;

begin

 // Создаём объект-парсер
 Parser := TDOMParser.Create;
 // и источник данных
 Src := TXMLInputSource.Create(AStream);
 // Включаем проверку достоверности
 Parser.Options.Validate := True;
 // Назначаем обработчик ошибок, который будет получать уведомления о них
 Parser.OnError := @ErrorHandler;
 // А теперь работаем
 Parser.Parse(Src, TheDoc);
 // ... и убираем за собой :)
 Src.Free;
 Parser.Free;

end;

procedure TMyObject.ErrorHandler(E: EXMLReadError); begin

 if E.Severity = esError then  // Нас интересуют только ошибки проверки достоверности
   writeln(E.Message);

end; </delphi>

Пробельные символы

Если необходимо сохранять пробельные символы в тексте узлов, следует пользоваться вышеприведённым методом загрузки XML документа. По умолчанию начальные пробельные символы игнорируются, именно поэтому функция ReadXML(...) пропускает все пробельные символы в начале текста узлов. Перед вызовом Parser.Parse(Src, TheDoc) добавьте следующую строку:

<delphi>Parser.Options.PreserveWhitespace := True;</delphi>

После этого парсер будет оставлять все пробельные символы, включая все переводы строк, добавленные для повышения читаемости XML документа!

Генерация файла XML

Ниже приведён код для записи XML в файле. (Взято из обучающей программы в блоге DeveLazarus). Пожалуйста помните, что модули DOM и XMLWrite должны быть включены в Uses

<delphi> unit Unit1;

{$mode objfpc}{$H+}

interface

uses

 Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs, StdCtrls,
 DOM, XMLWrite;

type

 { TForm1 }
 TForm1 = class(TForm)
   Button1: TButton;
   Label1: TLabel;
   Label2: TLabel;
   procedure Button1Click(Sender: TObject);
 private
   { private declarations }
 public
   { public declarations }
 end;
 

var

 Form1: TForm1;
 

implementation

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject); var

 xdoc: TXMLDocument;                                  // переменная документа
 RootNode, parentNode, nofilho: TDOMNode;             // переменная узла документа

begin

 //Создаём документ
 xdoc := TXMLDocument.create;
 //Создаём корневой узел
 RootNode := xdoc.CreateElement('register');
 Xdoc.Appendchild(RootNode);                           // Добавляем корневой узел в документ
 //Создаём родительский узел
 RootNode:= xdoc.DocumentElement;
 parentNode := xdoc.CreateElement('usuario');
 TDOMElement(parentNode).SetAttribute('id', '001');       // создаём атрибуты родительского узла
 RootNode.Appendchild(parentNode);                        // добавляем родительский узел
 //Создаём дочерний узел
 parentNode := xdoc.CreateElement('nome');                
 //TDOMElement(parentNode).SetAttribute('sexo', 'M');     // создаём его атрибуты
 nofilho := xdoc.CreateTextNode('Fernando');              // вставляем значение в узел
 parentNode.Appendchild(nofilho);                         // сохраняем узел
 RootNode.ChildNodes.Item[0].AppendChild(parentNode);     // вставляем дочерний узел в соответствующий родительский
 //Создаём ещё одиндочерний узел
 parentNode := xdoc.CreateElement('idade');               
 //TDOMElement(parentNode).SetAttribute('ano', '1976');   // создаём его атрибуты
 nofilho := xdoc.CreateTextNode('32');                    // вставляем значение в узел
 parentNode.Appendchild(nofilho);                         // сохраняем узел
 RootNode.ChildNodes.Item[0].AppendChild(parentNode);    // вставляем дочерний узел в соответствующий родительский
 writeXMLFile(xDoc,'teste.xml');                          // записываем всё в XML-файл
 Xdoc.free;                                               // освобождаем память

end;

initialization

 {$I unit1.lrs}

end. </delphi> Вот такой XML-файл у нас должен получится в результате: <xml> <?xml version="1.0"?> <register>

 <usuario id="001">
   <nome>Fernando</nome>
   <idade>32</idade>
 </usuario>

</register> </xml>

--Fernandosinesio 22:28, 24 April 2008 (CEST)fernandosinesio@gmail.com

Кодировки

Начинаясь с ревизии SVN 12582, XMLReader может читать данные в любой кодировке при условии использования внешнего декодера. Смотрите XML_Decoders/ru для более детальной информаци.

Согласно спецификации XML, атрибут encoding в первой строке XML является необязательным в случае, если фактическая кодировка - UTF-8 или UTF-16 (что определяется наличием BOM). В версии Lazarus 0.9.26 TXMLDocument имеет свойство Encoding, но оно игнорируется. При этом процедура WriteXMLFile всегда использует кодировку UTF-8 и не генерирует атрибут encoding в первой строке файла XML.

Дополнительные ссылки

  • TXMLPropStorage использование TXMLPropStorage для сохранения настроек программы.