XML Tutorial/ru

From Free Pascal wiki
Jump to: navigation, search

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-данными по принципу нарастающей сложности.

Использование: Юникод или Ansi

FPC предоставляет XML модули, использующие кодировку ANSI. Поэтому, они могут различаться в зависимости от платформы, в которой выполняется код и не иметь поддержку Юникода. Lazarus, в свою очередь, предоставляет собственный набор XML модулей, расположенных в пакете LazUtils. Они полностью поддерживают Юникод в формате UTF-8 и не зависят от выполняющей код платформы. Эти модули взаимозаменяемы и их использование определяется лишь наличием в списке uses.

Следующие модули FPC XML используют системную кодировку:

  • DOM
  • XMLRead
  • XMLWrite
  • XMLCfg
  • XMLUtils
  • XMLStreaming

Эти модули предоставлены Lazarus XML и имеют поддержку Юникода в формате UTF-8:

  • laz2_DOM
  • laz2_XMLRead
  • laz2_XMLWrite
  • laz2_XMLCfg
  • laz2_XMLUtils
  • laz_XMLStreaming.

Не все из них нужны в каждом примере. Однако, вам понадобится DOM, т.к. в нем содержится несколько типов, включая TXMLDocument.

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

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

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

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

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

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

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

 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;

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

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

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

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

Ниже приведён XML-файл с именем 'C:\Programs\test.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>

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

 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;

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

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

Загрузка XML в TreeView

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

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

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;

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

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

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

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

   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;

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

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;

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

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

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;

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

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

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

  <?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>

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

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

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;

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

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

Parser.Options.PreserveWhitespace := True;

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

Потоковое чтение

Обработка XML с использованием DOM, требует загрузки всего документа в память. Это может быть не желательно или невозможно, если документ огромен. FCL предоставляет функции для чтения одного узла данных XML за один раз, используя класс TXMLReader и его потомков. Класс TXMLReader похож на класс XmlReader платформы .NET. Ниже приведен простой пример использования TXmlReader:

uses
  Classes,xmlreader,xmltextreader,xmlutils;
 
procedure readme(AStream: TStream);
var
  xtr: TXmlReader;
  settings: TXMLReaderSettings;
  inp: TXMLInputSource;
begin
  settings := TXMLReaderSettings.Create;
  try
    settings.PreserveWhiteSpace := True;
    settings.Namespaces := True;
    inp := TXMLInputSource.Create(AStream);
    try
      xtr := TXmlTextReader.Create(inp,settings);
      try
        // Здесь начинается чтение
        while xtr.Read do
        begin
          write(xtr.NodeType:25);
          if xtr.name<>'' then
            write(xtr.Name:9)
          else
            write('*no name* ');
          write(xtr.Value);
          writeln;
          if xtr.NodeType=ntElement then
          begin
            // вывод атрибутов
            if xtr.MoveToFirstAttribute then
            begin
              repeat
                writeln('---',xtr.NodeType:21,xtr.Name:10,xtr.Value:10);
              until not xtr.MoveToNextAttribute;
              xtr.MoveToContent;
            end;  
          end;
        end;
      // Очистка  
      finally
        xtr.Free;
      end;
    finally
      inp.Free;
    end;
  finally
    settings.Free;
  end;
end;

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

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

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.

Вот такой XML-файл у нас должен получится в результате:

<?xml version="1.0"?>
<register>
  <usuario id="001">
    <nome>Fernando</nome>
    <idade>32</idade>
  </usuario>
</register>

--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 для сохранения настроек программы.

Смотрите также