Использование XML Document Object Model
Использование XML DOM в Borland Delphi
XML-документ — объект XMLDOMDocument
Ветвь документа — объект XMLDOMNode
Перемещение по дереву документа
Набор ветвей — объект XMLDOMNodeList
Создание и сохранение документов
Язык XML все чаще используется для хранения информации, обмена ею между приложениями и Web-узлами. Во многих приложениях этот язык применяется в качестве базового для хранения данных, в других — для экспортирования и импортирования XML-данных. Из этого следует, что разработчикам пора задуматься над тем, как можно использовать XML-данные в собственных приложениях.
В этой статье мы рассмотрим XML Document Object Model (DOM) и ее реализацию фирмой Microsoft — Microsoft XML DOM.
XML DOM — это объектная модель, предоставляющая в распоряжение разработчика объекты для загрузки и обработки XML-файлов. Объектная модель состоит из следующих основных объектов: XMLDOMDocument, XMLDOMNodeList, XMLDOMNode, XMLDOMNamedNodeMap и XMLDOMParseError. Каждый из этих объектов (кроме XMLDOMParseError) содержит свойства и методы, позволяющие получать информацию об объекте, манипулировать значениями и структурой объекта, а также перемещаться по структуре XML-документа.
Рассмотрим основные объекты XML DOM и приведем несколько примеров их использования в Borland Delphi.
Использование XML DOM в Borland Delphi
Для того чтобы использовать Microsoft XML DOM в Delphi-приложениях, необходимо подключить к проекту соответствующую библиотеку типов. Для этого мы выполняем команду Project | Import Type Library и в диалоговой панели Import Type Library выбираем библиотеку Microsoft XML version 2.0 (Version 2.0), которая обычно находится в файле Windows\System\MSXML.DLL
После нажатия кнопки Create Unit будет создан интерфейсный модуль MSXML_TLB, который позволит нам воспользоваться объектами XML DOM: DOMDocument, XMLDocument, XMLHTTPRequest и рядом других, реализованных в библиотеке MSXML.DLL. Ссылка на модуль MSXML_TLB должна быть указана в списке Uses.
Устройство XML DOM
Document Object Model представляет XML-документ в виде древовидной структуры, состоящей из ветвей. Программные интерфейсы XML DOM позволяют приложениям перемещаться по дереву документа и манипулировать его ветвями. Каждая ветвь может иметь специфический тип (DOMNodeType), согласно которому определяются родительская и дочерние ветви. В большинстве XML-документов можно встретить ветви типа element, attribute и text. Атрибуты (attribute) представляют собой особый вид ветви и не являются дочерними ветвями. Для управления атрибутами используются специальные методы, предоставляемые объектами XML DOM.
Помимо реализации рекомендованных World Wide Web Consortium (W3C) интерфейсов, Microsoft XML DOM содержит методы, поддерживающие XSL, XSL Patterns, Namespaces и типы данных. Например, метод SelectNodes позволяет использовать синтаксис шаблонов XSL (XSL Pattern Syntax) для поиска ветвей по определенному контексту, а метод TransformNode поддерживает использование XSL для выполнения трансформаций.
Тестовый XML-документ
В качестве примера XML-документа возьмем каталог музыкальных CD-ROM, который имеет следующую структуру:
<?xml version=”1.0"?> <CATALOG> <CD> <TITLE>Empire Burlesque</TITLE> <ARTIST>Bob Dylan</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>Columbia</COMPANY> <PRICE>10.90</PRICE> <YEAR>1985</YEAR> </CD> <CD> <TITLE>Hide your heart</TITLE> <ARTIST>Bonnie Tylor</ARTIST> <COUNTRY>UK</COUNTRY> <COMPANY>CBS Records</COMPANY> <PRICE>9.90</PRICE> <YEAR>1988</YEAR> </CD> ... <CD> <TITLE>Unchain my heart</TITLE> <ARTIST>Joe Cocker</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>EMI</COMPANY> <PRICE>8.20</PRICE> <YEAR>1987</YEAR> </CD> </CATALOG>
Теперь мы готовы приступить к рассмотрению объектной модели XML DOM, знакомство с которой начнем с объекта XMLDOMDocument.
XML-документ — объект XMLDOMDocument
Работа с XML-документом начинается с его загрузки. Для этого мы используем метод Load, который имеет всего один параметр, указывающий URL загружаемого документа. При загрузке файлов с локального диска указывается только полное имя файла (протокол file:/// в данном случае можно опустить). Если XML-документ хранится в виде строки, для загрузки такого документа следует использовать метод LoadXML.
Для управления способом загрузки документа (синхронный или асинхронный) используется свойство Async. По умолчанию это свойство имеет значение True, указывающее на то, что документ загружается асинхронно и управление возвращается приложению еще до полной загрузки документа. В противном случае документ загружается синхронно, и тогда приходится проверять значение свойства ReadyState, чтобы узнать, загрузился документ или нет. Также можно создать обработчик события OnReadyStateChange, который получит управление при изменении значения свойства ReadyState.
Ниже показано, как загрузить XML-документ, используя метод Load:
uses ... MSXML_TLB ... procedure TForm1.Button1Click(Sender: TObject); var XMLDoc : IXMLDOMDocument; begin XMLDoc := CoDOMDocument.Create; XMLDoc.Async := False; XMLDoc.Load(‘C:\DATA\DATA.xml’); // // Здесь располагается код, манипулирующий // XML-документом и его ветвями // XMLDoc := Nil; end;
После того как документ загружен, мы можем обратиться к его свойствам. Так, свойство NodeName будет содержать значение #document, свойство NodeTypeString — значение document, свойство URL — значение file:///C:/DATA/DATA.xml.
Обработка ошибoк
Особый интерес представляют свойства, связанные с обработкой документа при его загрузке. Так, свойство ParseError возвращает объект XMLDOMParseError, содержащий информацию об ошибке, возникшей в процессе обработки документа.
Чтобы написать обработчик ошибки, можно добавить следующий код:
var XMLError : IXMLDOMParseError; ... XMLDoc.Load(‘C:\DATA\DATA.xml’); XMLError := XMLDoc.ParseError; If XMLError.ErrorCode <> 0 Then // // Здесь мы обрабатываем ошибку // Else Memo1.Lines.Add(XMLDoc.XML); ... XMLDoc := Nil;
Чтобы узнать, какая информация возвращается в случае ошибки, изменим следующий элемент каталога:
<CD> <TITLE>Empire Burlesque</TITLE> <ARTIST>Bob Dylan</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>Columbia</COMPANY> <PRICE>10.90</PRICE> <YEAR>1985</YEAR> </CD>
убрав закрывающий элемент <TITLE> во второй строке:
<CD> <TITLE>Empire Burlesque <ARTIST>Bob Dylan</ARTIST> <COUNTRY>USA</COUNTRY> <COMPANY>Columbia</COMPANY> <PRICE>10.90</PRICE> <YEAR>1985</YEAR> </CD>
Теперь напишем код, возвращающий значения свойств объекта XMLDOMParseError:
XMLError := XMLDoc.ParseError; If XMLError.ErrorCode <> 0 Then With XMLError, Memo1.Lines do begin Add(‘Файл : ‘ + URL); Add(‘Код : ‘ + IntToStr(ErrorCode)); Add(‘Ошибка : ‘ + Reason); Add(‘Текст : ‘ + SrcText); Add(‘Строка : ‘ + IntToStr(Line)); Add(‘Позиция: ‘ + IntToStr(LinePos)); end Else Memo1.Lines.Add(XMLDoc.XML); End;
и выполним наше приложение. В результате получаем следующую информацию об ошибке .
Как видно из приведенного примера, возвращаемой объектом XMLDOMParseError информации вполне достаточно для того, чтобы локализовать ошибку и понять причину ее возникновения.
Теперь восстановим закрывающий элемент <TITLE> в нашем документе и продолжим обсуждение XML DOM.
Доступ к дереву документа
Для доступа к дереву документа можно либо получить корневой элемент и затем перебрать его дочерние ветви, либо найти какую-то специфическую ветвь. В первом случае мы получаем корневой элемент через свойство DocumentElement, которое возвращает объект типа XMLDOMNode. Ниже показано, как воспользоваться свойством DocumentElement для того, чтобы получить содержимое каждого дочернего элемента:
var Node : IXMLDOMNode; Root : IXMLDOMElement; I : Integer; ... Root := XMLDoc.DocumentElement; For I := 0 to Root.ChildNodes.Length-1 do Begin Node := Root.ChildNodes.Item[I]; Memo1.Lines.Add(Node.Text); End;
Для нашего XML-документа мы получим следующий текст .
Если нас интересует какая-то специфическая ветвь или ветвь уровнем ниже первой дочерней ветви, мы можем воспользоваться либо методом NodeFromID, либо методом GetElementByTagName объекта XMLDOMDocument.
Метод NodeFromID требует указания уникального идентификатора, определенного в XML Schema или Document Type Definition (DTD), и возвращает ветвь с этим идентификатором.
Метод GetElementByTagName требует указания строки со специфическим элементом (тэгом) и возвращает все ветви с данным элементом. Ниже показано, как использовать данный метод для нахождения всех исполнителей в нашем каталоге CD-ROM:
Nodes : IXMLDOMNodeList; Node : IXMLDOMNode; ... Nodes := XMLDoc.GetElementsByTagName(‘ARTIST’); For I := 0 to Nodes.Length-1 do Begin Node := Nodes.Item[I]; Memo1.Lines.Add(Node.Text); End;
Для нашего XML-документа мы получим следующий текст
Отметим, что метод SelectNodes объекта XMLDOMNode обеспечивает более гибкий способ для доступа к ветвям документа. Но об этом чуть ниже.
Ветвь документа — объект XMLDOMNode
Объект XMLDOMNode представляет собой ветвь документа. Мы уже сталкивались с этим объектом, когда получали корневой элемент документа:
Root := XMLDoc.DocumentElement;
Для получения информации о ветви XML-документа можно использовать свойства объекта XMLDOMNode (табл. 1).
Для доступа к данным, хранимым в ветви, обычно используют либо свойство NodeValue (доступно для атрибутов, текстовых ветвей, комментариев, инструкций по обработке и секций CDATA), либо свойство Text, возвращающее текстовое содержимое ветви, либо свойство NodeTypedValue. Последнее, однако, может использоваться только для ветвей с типизованными элементами.
Перемещение по дереву документа
Объект XMLDOMNode предоставляет множество способов для перемещения по дереву документа. Например, для доступа к родительской ветви используется свойство ParentNode (тип XMLDOMNode), доступ к дочерним ветвям осуществляется через свойства ChildNodes (тип XMLDOMNodeList), FirstChild и LastChild (тип XMLDOMNode) и т.д. Свойство OwnerDocument возвращает объект типа XMLDOMDocument, идентифицирующий сам XML-документ. Перечисленные выше свойства позволяют легко перемещаться по дереву документа.
Теперь переберем все ветви XML-документа:
Root := XMLDoc.DocumentElement; For I := 0 to Root.ChildNodes.Length-1 do Begin Node := Root.ChildNodes.Item[I]; If Node.HasChildNodes Then GetChilds(Node,0); End;
С помощью процедуры GetChilds перебираются все дочерние ветви указанной ветви (первый параметр) для данного уровня (второй параметр). Текст процедуры GetChilds выглядит так, как показано в листинге 1.
Как уже отмечалось выше, SelectNodes объекта XMLDOMNode обеспечивает более гибкий способ доступа к ветвям документа. Кроме того, существует метод SelectSingleNode, возвращающий только первую ветвь документа. Оба эти метода позволяют задавать XSL-шаблоны для поиска ветвей.
Рассмотрим процесс использования метода SelectNodes для извлечения всех ветвей, у которых есть ветвь CD и подветвь PRICE:
Root := XMLDoc.DocumentElement; Nodes := Root.SelectNodes(‘CD/PRICE’);
В коллекцию Nodes будут помещены все подветви PRICE ветви CD. К обсуждению XSL-шаблонов вернемся чуть позже.
Манипуляция дочерними ветвями
Для манипуляции дочерними ветвями мы можем воспользоваться методами объекта XMLDOMNode (табл. 2).
Для того чтобы полностью удалить запись о первом диске, необходимо выполнить следующий код :
var XMLDoc : IXMLDOMDocument; Root : IXMLDOMNode; Node : IXMLDOMNode; XMLDoc := CoDOMDocument.Create; XMLDoc.Async := False; XMLDoc.Load(‘C:\DATA\DATA.xml’); // Получить корневой элемент Root := XMLDoc.DocumentElement; Node := Root; // Удалить первую дочернюю ветвь Node.RemoveChild(Node.FirstChild);
Обратите внимание на то, что в данном примере мы удаляем первую дочернюю ветвь. Как удалить первый элемент первой дочерней ветви, показано ниже :
var XMLDoc : IXMLDOMDocument; Root : IXMLDOMNode; Node : IXMLDOMNode; XMLDoc := CoDOMDocument.Create; XMLDoc.Async := False; XMLDoc.Load(‘C:\DATA\DATA.xml’); // Получить корневой элемент Root := XMLDoc.DocumentElement; // и первую дочернюю ветвь Node := Root.FirstChild; // Удалить первую дочернюю ветвь Node.RemoveChild(Node.FirstChild);
В приведенном выше примере мы удалили не первую ветвь <CD>…</CD>, а первый элемент ветви — <TITLE>…</TITLE>.
Теперь добавим новую ветвь. Ниже приведен код, показывающий, как добавить новую запись о музыкальном CD-ROM :
var NewNode : IXMLDOMNode; Child : IXMLDOMNode; ... // Создадим новую ветвь - <CD> NewNode := XMLDoc.CreateNode(1, ‘CD’, ‘’); // Добавим элемент <TITLE> Child := XMLDoc.CreateNode(1,‘TITLE’,‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ‘Pink Floyd’; // Добавим элемент <ARTIST> Child := XMLDoc.CreateNode(1, ‘ARTIST’, ‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ‘Division Bell’; // Добавим элемент <COUNTRY> Child := XMLDoc.CreateNode(1, ‘COUNTRY’, ‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ‘UK’; // Добавим элемент <COMPANY> Child := XMLDoc.CreateNode(1, ‘COMPANY’, ‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ‘EMI Records Ltd.’; // Добавим элемент <PRICE> Child := XMLDoc.CreateNode(1, ‘PRICE’, ‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ’11.99'; // Добавим элемент <YEAR> Child := XMLDoc.CreateNode(1, ‘YEAR’, ‘’); // Добавим элемент NewNode.AppendChild(Child); // И установим его значение Child.Text := ‘1994’; // И добавим ветвь Root.AppendChild(NewNode); ...
Приведенный выше код показывает следующую последовательность действий по добавлению новой ветви:
- Создание новой ветви методом CreateNode:
- создание элемента методом CreateNode;
- добавление элемента к ветви методом AppendChild;
- установка значения элемента через свойство Text;
- … повторить для всех элементов.
- Добавление новой ветви к документу методом AppendChild.
Напомним, что метод AppendChild добавляет ветвь в конец дерева. Для того чтобы добавить ветвь в конкретное место дерева, необходимо использовать метод InsertBefore.
Трансформации
Два метода объекта XMLDOMNode — TransformNode и TransformNodeToObject — могут использоваться для трансформации ветви в строку или объект. Эти методы базируются на XSL-синтаксисе.
Набор ветвей — объект XMLDOMNodeList
Объект XMLNodeList содержит список ветвей, который может быть построен с помощью методов SelectNodes или GetElementsByTagName, а также получен из свойства ChildNodes.
Мы уже рассматривали использование этого объекта в примере, приведенном в разделе «Перемещение по дереву документа». Здесь же мы приведем некоторые теоретические замечания.
Число ветвей в списке может быть получено как значение свойства Length. Ветви имеют индексы от 0 до Length-1, и каждая отдельная ветвь доступна через элемент массива Item с соответствующим индексом.
Перемещение по списку ветвей также может осуществляться с помощью метода NextNode, возвращающего следующую ветвь в списке, или Nil, если текущая ветвь — последняя. Чтобы вернуться к началу списка, следует вызвать метод Reset.
Создание и сохранение документов
Итак, мы рассмотрели, как можно добавлять ветви и элементы в существующие XML-документы. Теперь создадим XML-документ «на лету». Прежде всего напомним, что документ может быть загружен не только из URL, но и из обычной строки. Ниже показано, как создать корневой элемент, который затем может использоваться для динамического построения остальных элементов (что мы уже рассмотрели в разделе «Манипуляция дочерними ветвями»):
var XMLDoc : IXMLDOMDocument; Root : IXMLDOMNode; Node : IXMLDOMNode; S : WideString; ... S := ‘<CATALOG></CATALOG>’; XMLDoc := CoDOMDocument.Create; XMLDoc.Async := False; XMLDoc.LoadXML(S); Root := XMLDoc.DocumentElement; Node := XMLDoc.CreateNode(1, ‘CD’, ‘’); Root.AppendChild(Node); Memo1.Lines.Add(XMLDoc.XML); ... XMLDoc := Nil;
После построения XML-документа сохраним его в файле с помощью метода Save. Например:
XMLDoc.Save(‘C:\DATA\NEWCD.XML’);
Помимо сохранения в файле метод Save позволяет сохранять XML-документ в новом объекте XMLDOMDocument. В этом случае происходит полная обработка документа и, как следствие, проверка его структуры и синтаксиса. Ниже показано, как сохранить документ в другом объекте:
procedure TForm1.Button2Click(Sender: TObject); var XMLDoc2 : IXMLDOMDocument; begin XMLDoc2 := CoDOMDocument.Create; XMLDoc.Save(XMLDoc2); Memo2.Lines.Add(XMLDoc2.XML); ... XMLDoc2 := Nil; end;
В заключение отметим, что метод Save также позволяет сохранять XML-документ в другие COM-объекты, поддерживающие интерфейсы IStream, IPersistStream или IPersistStreamInit.
Использование XSL-шаблонов
Обсуждая метод SelectNodes объекта XMLDOMNode, мы упомянули о том, что он обеспечивает более гибкий способ доступа к ветвям документа. Гибкость заключается в том, что в качестве критерия для выбора ветвей можно указать XSL-шаблон. Такие шаблоны предоставляют мощный механизм для поиска информации в XML-документах. Например, для того, чтобы получить список всех названий музыкальных CD-ROM в нашем каталоге, можно выполнить следующий запрос:
Nodes := Root.SelectNodes(‘CD/TITLE’);
Чтобы узнать, диски каких исполнителей выпущены в США, запрос формируется следующим образом:
Nodes := Root.SelectNodes(‘CD[COUNTRY=”USA”]/ARTIST’);
Ниже показано, как найти первый диск в каталоге:
Nodes := Root.SelectNodes(‘CD[0]/TITLE’);
и последний:
Nodes := Root.SelectNodes(‘CD[end()]/TITLE’);
Чтобы найти диски Боба Дилана, можно выполнить следующий запрос:
Nodes := Root.SelectNodes(‘CD[$any$ ARTIST= ”Bob Dylan”]/TITLE’);
а чтобы получить список дисков, выпущенных после 1985 года, мы выполняем следующий запрос:
Nodes := Root.SelectNodes(‘CD[YEAR > “1985”]/TITLE’);
Более подробное обсуждение синтаксиса XSL требует отдельной публикации. Чтобы заинтриговать читателей и подтолкнуть к дальнейшим исследованиям, приведу всего один небольшой пример возможного использования XSL. Допустим, нам необходимо преобразовать наш каталог в обычную HTML-таблицу. Пользуясь традиционными способами, мы должны перебрать все ветви дерева и для каждого полученного элемента сформировать соответствующие тэги <TD>…</TD>.
Используя XSL, мы просто создаем шаблон (или таблицу стилей), в котором указываем, что и как надо преобразовать. Затем накладываем этот шаблон на наш каталог — и готово: перед нами текст XSL-шаблона, преобразующего каталог в таблицу (листинг 2).
Код для наложения XSL-шаблона на наш каталог выглядит так:
procedure TForm1.Button2Click(Sender: TObject); var XSLDoc : IXMLDOMDocument; begin XSLDoc := CoDOMDocument.Create; XSLDoc.Load(‘C:\DATA\DATA.xsl’); Memo2.Text := XMLDoc.TransformNode(XSLDoc); XSLDoc := Nil; end;
Рядом показан результат преобразования каталога в HTML-таблицу.
Завершая наше обсуждение XSL, следует сказать, что в настоящее время этот язык активно используется для трансформации между различными XML-документами, а также для форматирования документов.
Заключение
По вполне понятным причинам в одной статье невозможно рассмотреть все объекты Microsoft XML DOM и привести примеры их использования. Здесь мы лишь коснулись основных вопросов использования XML DOM в приложениях. В табл. 3 показаны все объекты, реализованные в Microsoft XML DOM.
КомпьютерПресс 12'2000