XML Tutorial/ru
Template:Руководство по работе с XML
Введение
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
Ниже приведён код для записи 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); // сохраняем узел .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.
Дополнительные ссылки
- W3Schools Руководство по Xml
- Thomas Zastrow article Работа с XML-файлами