 FreePascal
		FreePascal
		| Работа с XML-файлами | 21.03.2008 Вадим Исаев | 
Авторское право Thomas Zastrow.
Перевод на русский язык Вадим Исаев.
Загрузить код примера
Document Object Model (DOM) - официальная спецификация W3C. Она описывает как разобрать XML-структуру, элементы адреса, аттрибуты и содержание. По сравнению с SAX-подходом DOM более удобна для пользователя, но нуждается в хорошем процессоре и бОльшем количестве оперативной памяти.
FPC поставляется с модулем dom.pp (fcl-xml/dom.pp). 
Эта статья-справка помогает разбирать XML-файл, вместе с 
несколькими строчками кода.
Создайте новое приложение в Lazarus и поместите на форму некоторые компоненты:
 
На форме есть три TEdit-поля, один TMemo, две кнопки и OpenFile-диалог.
В первом TEdit поле (названом XMLFile) помещается имя 
XML-файла, который должен быть разобран. 
Кнопка около него для того, чтобы открыть XML-файл через окно диалога выбора 
файла. OnClick-процедура кнопки имеет код:
procedure TForm1.Button1Click(Sender: TObject);
begin
  if opendialog1.execute then
    XMLFile.text := opendialog1.filename;
end;
Во втором TEdit-поле вводится название XML-элемента, который ищется в XML-файле, Третье TEdit-поле хранит название аттрибута, который нужно найти. Эти компоненты названы element2find и attribut2find.
TMemo (названный output) - чтобы хранить вывод анализатора. И последнее - кнопка с заголовком "Начать", которая запустит парсинг.
В строке Uses Вашей программы должны быть указаны 
модули XMLRead и dom.
OnClick-процедура кнопки "Начать":
procedure TForm1.Button2Click(Sender: TObject);
var doc:txmldocument;
    temp:TDomNode;
begin
  output.lines.clear;
  ReadXMLFile(doc, XMLFile.text);
  temp := doc.DocumentElement;
  ParseXML(temp);
end;
ReadXMLFile открывает XML-документ и присваивает его 
переменной doc. 
Затем корневой элемент присваивается переменной temp. 
Процедура ParseXMLM сделает должным образом парсинг.
 
Отношения узлов в XML-структуре
Итак, ParseXML начинается с корневого элемента 
XML-файла (Root). Этот элемент будет проанализирован и, если будут 
какие-нибудь дочерние узлы, то процедура вызовет себя снова, но теперь с 
параметром - дочерним элементом.
Шаг за шагом - процедура ParseXML:
  E := knoten as TDomElement;
  N := E.FirstChild;
Текущий XML-элемент - давайте называть его контекстный узел - назначен на 
E как TDOMElement. N берет первый дочерний элемент (Children) 
контекстного узла.
Посмотрим на цикл-петлю:
  While (N <> NIL) Do
    ...
  N := N.NextSibling; 
Цикл пробегает всех узлов-братьев (соседей :) ) контекстного узла. В конце цикла следующий узел-брат (сосед) контекстного узла становился контекстным узлом.
Снова в начале петли:
  addToOutput(N);
Процедуру addToOutput вызывают с контекстным узлом 
как параметром. Эта процедура используется, чтобы построить строку, 
которая добавляется в конец TMemo-компонента, названному "output". 
Сначала, строка s получает название узла, который в параметре, чтобы вывести 
его: 
  s := 'Name:' + N.NodeName;
Помните, что на нашей форме есть TEdit-компонент по имени element2find. Если 
название текущего узла - то же самое что и 
свойство Text этого компонента, значит мы нашли элемент, который 
искали:
  if N.NodeName = form1.element2find.text then
  begin
    s := s + ' Found element!';
  end;
Поиск Аттрибута (текстовое свойство TEdit-компонента attribut2find) немного более сложен:
if N.NodeName <> '#text' then
begin
  E := N as TDOMElement;
  i := E.attributes.count;
  s := s + ' Count Attribute ' + inttostr(i);
  if E[form1.attribut2find.text]<>'' then
    s := s + ' Found Attribut';
end; {nodename <> text}
Если содержание узла только текст, и нет больше узлов-братьев или дочерних 
узлов, то его название равно #text. И текст не 
может иметь никаких признаков. Затем, TDOMElement E назначен на 
этот узел. Теперь признаки могут являться признаками E.
Количество признаков присоединяется к строке s. Если условие if-запроса истинно, то элемент имеет признак, название которого тождественно свойству attribut2find.Text.
Последняя часть процедуры ParseXML проверяет, 
имеет ли контекстный узел дочерние узлы:
if N.HasChildNodes then
begin
  I := N.childnodes.count;
  sub := N.FirstChild;
  addToOutput(sub);
Если дело обстоит именно так, то первый дочерний узел контекстного узла 
назначается переменной sub. Снова вызывается процедура 
addToOutput, но теперь с параметром sub.
Следует начало рекурсии - если sub снова имеет 
какой-нибудь дочерний узел, процедура ParseXML 
вызывается снова с sub как параметр: 
if sub.haschildnodes then
begin
  ParseXML(sub);
end;
В этом пункте помните, что sub был первым дочерним узлом
текущего контекстного узла. Его узлы-братья присвоены переменной 
sub - таким образом они обрабатываются for-петлей:
for zaehler := 1 to i-1 do
begin
  sub := sub.NextSibling;
  addToOutput(sub);
  form1.refresh;
  if sub.HasChildNodes then
  begin
    ParseXML(sub);
  end;
end; {for zaehler}
Парсинг файла примера test.xml производит следующий вывод, с поиском элемента, 
названным two и аттрибутом a:
Name: one Count Attribute 0
Name: two Found element! Count Attribute 0
Name: #text
Name: three Count Attribute 2 Found Attribut
Name: child_of_three Count Attribute 0
Name: #text
Вот и все.
Авторское право Thomas Zastrow.
Перевел на русский язык Вадим Исаев
Загрузить код примера
Эта обучающая программа демонстрирует создание XML-структур.
 
Создайте новое Lazarus-приложение с TMemo-компонентом и кнопкой на форме.
 
Модули dom и XMLWrite должны быть включены в строку 
Uses Вашей программы.
Щелчок по кнопке создаст простые XML-структуры, сохранит их в файл и отобраит XML-код в TMemo.
Давайте взглянем на процедуру OnClick кнопки:
После создания документа (новый XML-документ), N и sub 
(два TDOMNodes), будет 
создан корневой элемент (названный "root") XML-файла, который,
в свою очередь будет назначен документу: 
  N := doc.CreateElement('root');
  doc.Appendchild(N);
Далее элемент N назначается корневым элементом. 
sub будет элементом, подчиненным элементу N 
("RootsFirstChild"). Добавляется аттрибут 
к sub с помощью 
 E приведенного к типу TDOMElement 
 (AttributeName = "SomeValue"). 
Теперь sub можно добавить к N: 
  N := doc.DocumentElement;
  sub := doc.CreateElement('RootsFirstChild');
  E := sub as TDOMElement;
  E['AttributeName']:='SomeValue';
  N.AppendChild(sub);
Другой подчиненный элемент ("RootsSecondChild") 
добавлен к корневому:
  sub := doc.CreateElement('RootsSecondChild');
  N.AppendChild(sub);
Теперь к последнему элементу можно добавить какой-нибудь текст. 
Текст, как содержание XML-узла, обрабатывается как дочерний узел:
Элементу N присваивается sub, а 
sub теперь будет текстовым содержанием 
(CreateTextNode('Text-content')). 
После этого sub добавляется к N: 
 
  N := sub;
  sub := doc.CreateTextNode('Text-content');
  N.AppendChild(sub);
Окончание процедуры.
XML-структура записывается в файл ("xml-file.xml") и файл 
загружается в компонент TMemo:
  writeXMLFile(doc, 'xml-file.xml');
  memo1.lines.clear;
  memo1.lines.loadfromfile('xml-file.xml');
  doc.free;
Полученый XML-файл:
  Text-content 
 
Авторское право Thomas Zastrow.
Перевод на русский язык Вадим Исаев.
Загрузить код примера
Во FreePascal есть модуль xmlcfg.pp. Этот модуль специально 
разработан для того, чтобы читать и писать данные конфигурации в форме пары 
ключ-значение. Основной формат файла конфигурации - XML, но использующий 
xmlcfg.pp программист не должен иметь дело со сложностью 
синтаксического анализа XML-данных.
Следующее небольшое Lazarus-приложение покажет Вам, как использовать модуль xmlcfg.
Создайте новое Lazarus-приложение и поместите на форму компоненты:
 
Включите в строку Uses Вашего приложения модуль xmlcfg!
На форме лежат три TEdit-компоненты с названиями:
Компоненты в левой группе, названые "Записать ключзначение", создают новое значение в файле конфигурации. Правая группа ("Прочитать ключзначение") - читают пару ключзначение из файла конфигурации.
Теперь, остается только ввести что-нибудь как пару ключзначение в двух 
TEdit-полях слева и щелкнуть левой кнопкой "OK". Например:
В файле myconfig.xml будет что-то вроде этого:
Здесь файл содержит ключ "MyName" со значением "Tom".
При создании формы создается переменная cfg типа 
TXMLConfig, которая отвечает за открытие или создание файла
конфигурации с именем myconfig.xml.
При закрытии формы файл закрывается и переменная освобождается.
Посмотрим на метод OnClick левой кнопки "OK" :
Procedure TForm1. Button1Click (Sender: TObject);
Var 
  s: string;
Begin
 cfg.SetValue(KeyName2write.text, Value2write.text);
 cfg.Flash;
 Messages.caption: = 'Ключзначение были сохранены!';
End;
Пара ключзначение (Keyname2write / Value2write) 
будет записана в файл, используя метод SetValue() объекта
cfg. После этого происходит принудительный сброс буфера в файл
с помощью метода Flash
Все очень просто, не так ли?
Столь же проста, как запись в файл конфигурации и чтение из него.
Щелчек по второй кнопке "OK" прочитает значение указанного в
KeyName2read ключа из файла конфигурации "myconfig.xml". 
Значение будет написано в метке Messages. 
Процедура OnClick второй (правой) кнопки "OK":
Procedure TForm1.Button2Click (Sender: TObject);
Var 
  s: string;
Begin
  s := cfg.GetValue(Keyname2read.text, ");
  Messages.caption := 'Значение' + KeyName2read.text + ':' + s;
End;
В переменную s заносится значение 
найденого ключа с помощью метода GetValue(). Посде этого выводим 
значение в Messages.
Замечание 1
Вы можете также писать/читать целочисленные и логические значения.
Замечание 2
Файл конфигурации может иметь иерархическую структуру. Разделяя названия ключей и значения к ним символами "/" можно получить более детализированные упорядоченные категории значений.
Например, создавая следующие пары ключзначение:
author/tom := chief
author/tom/email := chef@thomas-zastrow.de
мы получаем такой XML-файл:
Загрузить код примера
Поскольку примеры Томаса носят несколько академический характер, то было решено создать пример более практический, который выполняет некую полезную функцию. Пример будет получать курс валют (доллар, евро) с сайта Центробанка России и выводить полученые значения в табличку.
 
В строке Uses Вашей программы должны быть указаны 
модули XMLRead и dom.
На форме размещаются компоненты TPanel, TStringGrid, TButton. TPanel 
выравнивается по нижнему краю, а TStringGrid по оставшейся клиентской области.
В процедуру OnClick кнопки заносится процедура получения курса валют.
Для связи с интернетом был выбран компонент TIdHTTP из комплекта 
Indy для Lazarus.
Сначала составляется правильная ссылка, чтобы получить курс доллара на текущее число из набора констант и отформатированной даты:
  XMLURLD:= URL + FormatDateTime('dd''/''mm''/''yyyy', Now)+
           '&date_req2='+
           FormatDateTime('dd''/''mm''/''yyyy', Now)+
           DOLL;
Получаем строку с курсом доллара с сайта:
  s:=IdHTTP1.Get(XMLURLD);
Внимание!!А вот дальше я наситупил на первые грабли. :)
Оказывается FPC не работает с кодировками XML-файлов отличными от UTF.
А Центробанк нам выдает строку с кодировкой в заголовке 1251. Поэтому перед парсингом 
упоминание о кодировке приходится удалять:
  i:=Pos(es, s);
  Delete(s, i, Length(es)+1);
Далее присваиваем строку компоненту TStringList, сохраняем текстовое содержимое в файл "dollar.xml" и загружаем этот файл в компоненту типа TXMLDocument для разбора:
  st.Text:=s;
  st.SaveToFile(GetCurrentDir+'dollar.xml');
  ReadXMLFile(doc, GetCurrentDir+'dollar.xml');
Теперь получаем корневой элемент, находим у него дочерний узел и в цикле ищем дочерний узел, имя у которого "Nominal", в котором хранится номинал валюты:
  dn:=doc.DocumentElement;
  n:=dn.FirstChild;
  While n.NodeName<>'Nominal' Do
    n:=n.FirstChild;
Потом займемся поисками брата-узла номинала по имени "Value" где и хранится 
значение курса валюты.
Внимание!! Здесь я наступил на вторые грабли. :) Причем из-за 
того, что ленился прочитать спецификацию XML. Хотел сразу же у найденых братьев 
получить значение узла, но оказывается все что содержится в XML-документе - это
узлы, а значения находятся как бы внутри узла. Поэтому у найденых братьев 
следует взять дочерние узлы и только в них уже
содержаться значения номинала и курса:
  sub:=n.NextSibling;
  stg1.Cells[1,1]:=n.FirstChild.NodeValue;
  stg1.Cells[2,1]:=sub.FirstChild.NodeValue;
Ну вот и все. Для получения курса Евро в ссылке код доллара меняется на код Евро.
| FPC | 3.2.2 | release | 
| Lazarus | 3.2 | release | 
| MSE | 5.10.0 | release | 
| fpGUI | 1.4.1 | release |