Работа над клиентов для DelphiFeeds.ru идёт полным ходом — получил доступ к XML-RPC и теперь познаю всё тонкости работы с XML-RPC в Joomla!. Соответственно, теперь вся работа клиента будет строится на чтении XML-документов, содержащих информацию по публикациям и это обстоятельство (использование только XML) подтолкнуло меня на то, чтобы более детально разобраться с парсингом XML в Mac OS X. Как знать, вполне возможно, что с выходом Mobile Studio появится и Mac- или iOS-версия клиента (при условии, что он вообще будет востребован на этих платформах).
Как я уже говорил в предыдущей статье, посвященной работе с Mac OS X в Delphi XE3, в примерах к RAD Studio есть замечательный примерчик, демонстрирующий создание XML-документов с использованием интерфейсов из Foundation Framework. Вот его-то я и решил использовать как основу для своих будущих исследований возможностей Mac OS X.
Работая в ОС Windows мы привыкли к разнообразию библиотек и компонентов для решения самых разнообразных задач, в том числе и для работы с XML. Хочешь — бери MSXML, не нравится MSXML — бери NativeXML, OmniXML. Вариантов масса. Однако, когда мы пробуем создать что-либо, работающее в Mac OS X, возникает закономерный вопрос: что и, главное, как использовать? Можно, конечно, забраться на форум cyberforum.ru, окопаться в темах про XML и долго и упорно перенимать чужой опыт, а можно разобраться самим. Далеко не каждая библиотека Delphi, заявленная как кроссплатформенная успешно заработает под ОС от Apple, а изобретать очередной велосипед особенного желания как-то не возникает. Да и зачем? В OS X уже есть необходимые интерфейсы для работы с XML — так почему бы не начать именно с них?
Содержание
- NSXMLDocument — создание XML-документа, чтение/запись его свойств
- NSXMLNode и NSXMLElement — работа с узлами XML-документа
- Приложение XMLBrowser для Mac OS X
NSXMLDocument — создание XML-документа, чтение/запись его свойств
Представляет собой XML-документ, который может содержать сколько угодно дочерних узлов и только один корневой узел. Здесь реализованы методы чтения XML из локальных файлов, строк, URL, представления данных в виде plain text, html и т.д. Здесь же, соответственно, реализованы и основные методы для работы с узлами XML-документа. Рассмотрим несколько примеров работы с NSXMLDocument.
Пример №1. Загружаем XML-данные из файла.
function LoadXMLFromFile(const FileName: string): NSXMLDocument; var Data: NSData; begin //получаем NSData, загрузив данные из файла Data:=TNSData.Wrap(TNSData.OCClass.dataWithContentsOfFile(NSSTR(FileName))); //создаем пустой NSXMLDocument Result:=TNSXMLDocument.Create; //грузим XML-данные из NSData Result.initWithData(Data, NSXMLDocumentTidyXML,nil); end;
Здесь для загрузки данных в пустой NSXMLDocument мы воспользовались интерфейсом NSData, у которого использовали метод dataWithContentsOfFile для взятия данных из файла. Также просто мы можем получить NSXMLDocument и из простой строки.
Пример №2. Загрузка XML-данных из строки
function LoadXMLFromString(const XMLData: string): NSXMLDocument; begin //создали пустой NSXMLDocument Result:=TNSXMLDocument.Create; //создали NSString и передали его NSXMLDocument Result.initWithXMLString(NSSTR(XMLData), NSXMLDocumentTidyXML,nil); end;
Теперь посмотрим какую информацию об XML мы можем получить, используя только NSXMLDocument.
Пример №3. Чтение свойств XML-документа
У NSXMLDocument имеются следующие методы для получения и установки свойств документа:
Метод чтение свойства | Метод записи свойства | Описание | Тид данных |
characterEncoding | setCharacterEncoding | Кодировка документа | NSString |
documentContentKind | setDocumentContentKind | Тип содержимого документа. Может принимать одно из следующих значений:
|
LongWord |
DTD | setDTD | Описание схемы документа | NSXMLDTD |
isStandalone | setStandalone | Возвращает False, если документ не содержит внешних DTD | boolean |
MIMEType | setMIMEType | MIME-тип документ, например «text/xml» | NSString |
version | setVersion | Версия документа | NSString |
Так как большинство методов чтения свойств документа возвращают ссылки на интерфейсы, то не стоит читать свойства так:
XmlContent.Lines.Add('Character Encoding: '+XmlDoc.characterEncoding.UTF8String);
Так как вполне возможно, что метод вернет и nil. Организовать чтение свойств документа можно, например, так:
procedure TFrmXml.GetDocumentProperties(Document: NSXMLDocument; PropList: TStrings); var Version, Encoding, MimeType: NSString; begin Version := Document.Version; if Assigned(Version) then PropList.Add('Version: ' + Version.UTF8String) else PropList.Add('Version: Unknown'); Encoding := Document.characterEncoding; if Assigned(Encoding) then PropList.Add('Character Encoding: ' + Encoding.UTF8String) else PropList.Add('Encoding: Unknown'); PropList.Add('Standalone: ' + BoolToStr(Document.isStandalone, true)); case Document.documentContentKind of NSXMLDocumentXMLKind: PropList.Add('Content Kind: XML'); NSXMLDocumentHTMLKind: PropList.Add('Content Kind: HTML'); NSXMLDocumentXHTMLKind:PropList.Add('Content Kind: XHTML'); NSXMLDocumentTextKind: PropList.Add('Content Kind: Plain Text'); end; MimeType:=Document.MIMEType; if Assigned(MimeType) then PropList.Add('MimeType: '+MimeType.UTF8String) else PropList.Add('MimeType: Unknown')
Следующий момент работы с NSXMLDocument — чтение данных документа.
Пример №4. Чтение данных из XML-документа
Для получения данных их XML-документа у NSXMLDocument предусмотрены следующие методы:
Метод | Описание | Тип результата |
XMLData | Возвращает контент документа как NSData | NSData |
XMLDataWithOptions | Возвращает контент документа как NSData. Входной параметр options может принимать одно из следующих значений:
|
NSData |
rootElement | Возвращает корневой элемент документа | NSXMLElement |
Описание констант для XMLDataWithOptions можно посмотреть в официальной документации.
Для примера попробуем загрузить XML-документ из файла и прочитать данные о корневом элементе (с использованием XMLData и XMLDataWithOptions можно ознакомиться в официальном примере Embarcadero — XMLOnMac):
procedure GetRootInfo(Document: NSXMLDocument; PropList: TStrings); var Root: NSXMLElement; Name, URI, XMLString: NSString; begin Root:=Document.rootElement; if Assigned(Root) then begin Name:=Root.name; if Assigned(Name) then PropList.Add('Root Name: '+Name.UTF8String); URI:=Root.URI; if Assigned(URI) then PropList.Add('URI: '+URI.UTF8String); XMLString:=Root.XMLString; if Assigned(XMLString) then PropList.Add('XMLString: '+XMLString.UTF8String); end else PropList.Add('RRoot Element not assigned') end;
На этом примере мы вплотную подошли к работе со следующими интерфейсами — NSXMLNode и NSXMLElement. Посмотрим, что из себя представляют эти интерфейсы и как их можно использовать для работы с XML-данными.
NSXMLNode и NSXMLElement — работа с узлами XML-документа
NSXMLNode представляет собой узел XML-документа и содержит необходимые методы для работы с дочерними и родительскими элементами, атрибутами узла и т.д. NSXMLNode является родителем для NSXMLElement. Так, если NSXMLNode предоставляет доступ к таким свойствам как количество дочерних элементов, массив дочерних элементов, имя узла, уровень и т.д., то NSXMLElement расширяет возможности NSXMLNode, предоставляя доступ, например, к атрибутам узла.
Пример №1. Чтение имен дочерних узлов
procedure GetChilNodesNames(RootElement: NSXMLElement; NamesList: TStrings); var i,count:integer; Node: NSXMLElement; childs: NSArray; begin childs:=RootElement.children; count:=childs.count; for i:=0 to Count-1 do begin Node:=TNSXMLElement.Wrap(childs.objectAtIndex(i)); NamesList.Add(Node.name.UTF8String); GetNodeAttributes(Node,NamesList); end; end
Здесь для чтения имен дочерних узлов мы воспользовались методом children, который вернул нам массив NSArray, содержащий все дочерние узлы.
Следующий простой пример демонстрирует чтение атрибутов узла.
Пример №2. Чтение атрибутов узла
procedure TFrmXml.GetNodeAttributes(XMLNode: NSXMLElement; AttrList: TStrings); var Element: NSXMLElement; i:integer; AttrArray: NSArray; AttrNode: NSXMLNode; begin AttrArray:=XMLNode.attributes; if Assigned(AttrArray) then begin for I := 0 to AttrArray.count-1 do begin AttrNode:=TNSXMLNode.Wrap(AttrArray.objectAtIndex(i)); AttrList.Add('Attr Name: '+AttrNode.name.UTF8String+' Value: '+AttrNode.stringValue.UTF8String) end; end; end;
И теперь мы уже вплотную подошли к тому, чтобы полностью прочитать и разобрать XML-документ. Для этого мы напишем свое небольшое приложение, которое назовем просто — XMLBrowser.
Приложение XMLBrowser для Mac OS X
Наше приложение будет делать совсем не много, а именно:
- строить дерево всех узлов XML
- получать строковые значения узлов (stringValue)
- получать доступ к атрибутам узла и выводить их имена/значения в таблицу
- читать другие свойства узлов XML: уровень, индекс, имя узла, количество дочерних узлов и т.д.
Главная форма приложения у меня выглядит следующим образом:
Для работы с XML-документом определена переменная XmlDoc и следующие методы:
type TMainForm = class(TForm) [...] private XmlDoc: NSXMLDocument; function LoadXmlFromFile(const Filename: string):NSXMLDocument; procedure GenerateTree; procedure ReadNodePropertys(XPath:string); procedure ReadAttributes(XPath:string); public { Public declarations } end;
Метод LoadXmlFromFile в точности повторяет первый рассмотренный пример по работе с NSXMLDocument.
Метод GenerateTree выводит в TreeView названия всех узлов в XML-документа. Выглядит он следующим образом:
procedure TMainForm.GenerateTree; function AddNode(RTreeNode: TTreeViewItem; Element: NSXMLElement):TTreeViewItem; begin Result:=TTreeViewItem.Create(RTreeNode); Result.Parent:=RTreeNode; Result.Text:=Element.name.UTF8String; Result.TagString:=Element.XPath.UTF8String; RTreeNode.AddObject(Result); end; procedure ProcessNode(Node: NSXMLElement; TreeNode: TTreeViewItem); var childArr: NSArray; I: Integer; ChildNode: NSXMLElement; childTreeNode: TTreeViewItem; begin if Node.childCount=0 then Exit; childArr:=Node.children; for I := 0 to childArr.count-1 do begin ChildNode:=TNSXMLElement.Wrap(childArr.objectAtIndex(i)); if (ChildNode.kind=NSXMLDocumentKind)or(ChildNode.kind=NSXMLElementKind) then begin childTreeNode:=AddNode(TreeNode,ChildNode); if ChildNode.childCount>0 then ProcessNode(ChildNode,childTreeNode); end; end; end; var RootNode: NSXMLElement; TreeNode: TTreeViewItem; Arr: NSArray; I: Integer; begin XMLTreeView.Clear; //добавляем корневой узел в TreeView RootNode:=XmlDoc.rootElement; TreeNode:=TTreeViewItem.Create(XMLTreeView); TreeNode.Parent:=XMLTreeView; TreeNode.Text:=RootNode.name.UTF8String; TreeNode.TagString:=RootNode.XPath.UTF8String; XMLTreeView.AddObject(TreeNode); //читаем дочерние узлы Arr:=RootNode.children; for I := 0 to Arr.count-1 do begin RootNode:=TNSXMLElement.Wrap(Arr.objectAtIndex(i)); //обходим все дочерние узлы ProcessNode(RootNode,AddNode(TreeNode,RootNode)); end; end;
В принципе, эта процедура практически ничем не отличается от тех же процедур обхода XML-дерева в VCL и Windows. Различие лишь в том, что здесь для хранения информации об XML-узле мы используем свойство TagString узла TreeView и делаем мы это следующим образом:
function AddNode(RTreeNode: TTreeViewItem; Element: NSXMLElement):TTreeViewItem; begin [...] Result.TagString:=Element.XPath.UTF8String; [...] end;
Сохранив XPath XML-узла в TagString узла TreeView мы можем в любой момент получить значение NSXMLElement, что и было реализовано в обработчике OnClick TreeView:
procedure TMainForm.XMLTreeViewClick(Sender: TObject); begin ReadNodePropertys(XMLTreeView.Selected.TagString); ReadAttributes(XMLTreeView.Selected.TagString); end;
Здесь методам ReadNodePropertys и ReadAttributes в параметрах передается XPath узла, а сами методы реализуют заполнение двух таблиц на форме — свойств узла и его атрибутов:
procedure TMainForm.ReadNodePropertys(XPath: string); var Arr: NSArray; Element:NSXMLElement; begin Arr:=XmlDoc.nodesForXPath(NSSTR(XPath),nil); if Assigned(Arr) then begin Element:=TNSXMLElement.Wrap(Arr.objectAtIndex(0)); NodeStrValue.Text:=Element.stringValue.UTF8String; CommonPropsGrid.Cells[1,0]:=Element.XPath.UTF8String; CommonPropsGrid.Cells[1,1]:=Element.name.UTF8String; CommonPropsGrid.Cells[1,2]:=Element.localName.UTF8String; CommonPropsGrid.Cells[1,3]:=IntToStr(Element.index); CommonPropsGrid.Cells[1,4]:=IntToStr(Element.childCount); CommonPropsGrid.Cells[1,5]:=IntToStr(Element.level); end; end; procedure TMainForm.ReadAttributes(XPath: string); var Arr: NSArray; Element:NSXMLElement; Attributes: NSArray; I: Integer; AttrNode: NSXMLNode; begin AttributesGrid.RowCount:=0; //получаем узлы по XPath Arr:=XmlDoc.nodesForXPath(NSSTR(XPath),nil); if Assigned(Arr) then begin Element:=TNSXMLElement.Wrap(Arr.objectAtIndex(0)); Attributes:=Element.attributes; if Assigned(Attributes) then begin AttributesGrid.RowCount:=Attributes.count; for I := 0 to AttributesGrid.RowCount-1 do begin AttrNode:=TNSXMLNode.Wrap(Attributes.objectAtIndex(i)); AttributesGrid.Cells[0,i]:=AttrNode.name.UTF8String; AttributesGrid.Cells[1,i]:=AttrNode.stringValue.UTF8String; end; end; end; end;
В этим процедурах XPath всегда однозначно определяет конкретный узел в XML, поэтому при работе с массивом я только проверяю, что узел найден (массив определен) и не проверяю его размерность — в массиве гарантировано будет содержаться только один узел.
Теперь остается проверить работу нашего приложения. Для примера я взял два XML-файла:
- XML, содержащий ответ XML-RPC
- XML, описывающий скин для видео-плеера.
Первый XML-документ:
Второй XML-документ:
Вот так, используя всего пару интерфейсов из Foundation Framework можно разбирать XML-документы. Приведенный выше пример программы — основа для дальнейшего изучения вопроса, т.к. ещё достаточно много моментов по работе с XML в Mac OS X осталось «за бортом», например, как работать с namespase, CDATA или комментариями в XML.
[…] Также про работу с XML в Mac OS Вы можете узнать из статьи "Delphi XE3: работа с XML в Mac OS X" […]