Работа с 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;
Ну вот и все. Для получения курса Евро в ссылке код доллара меняется на код Евро.