После того, как мы рассмотрели возможность превода данных объекта в XML следует перейти к следующей задаче. Задача состоит в реализации обратного процесса, а именно - загрузки XML данных в объект.
Загрузка XML данных в объект, или десериализация, представляет собой более сложный процесс, т.к. в ходе его необходимо осуществить корректный разбор текстового XML документа на предмет инициализации содержащимися в нем данными заданного объекта.
Примем ряд упрощений, которые сократят число проверок корректности входящего XML документа к минимуму. Первое, что необходимо делать, тек это проверять соответствие тега верхнего уровня имени класса нашего объекта. Синтаксическая правильность документа будет проверяться в ходе загрузки данных. При необходимости более жесткой проверки загружаемых XML документов можно привлечь, к примеру, парсер MSXML. Последний поможет нам проверить документ на синтаксическую, а также семантическую корректность при наличии соответствующего DTD.
Первое, что следует реализовать, это процедура верхнего уровня, которая получает объект для инициализации, а также потоковый источник данных с текстом XML документа.
var// Буфер, в котором находится XML документ
Buffer: PChar;
// Указатель на текущее положение парсера XML документа
TokenPtr: PChar;
{
Загружает в компонент данные из потока с XML-кодом.
Вход:
Component - компонент для конвертации
Stream - источник загрузки XML
Предусловия:
Объект Component должен быть создан до вызова процедуры
}procedure DeSerialize(Component: TObject; Stream: TStream);
begin
GetMem(Buffer, Stream.Size);
try{ Получаем данные из потока }
Stream.read(Buffer[0], Stream.Size + 1);
{ Устанавливаем текущий указатель чтения данных }
TokenPtr := Buffer;
{ Вызываем загрузчик }
DeSerializeInternal(Component, Component.ClassName);
finally
FreeMem(Buffer);
end;
end;
Следующий код занимается тривиальным разбором XML текта. Ищется первый открывающий тег, затем его закрывающая пара. Найденная пара содержит в себе данные для свойств объекта. Внутри найденной пары тегов последовательно выбираются теги (TagName) и текст их содержания (TagValue). Эти теги предположительно соответствуют свойствам объекта, что мы тут же и проверяем.
Среди свойств объекта отыскивается через FindProperty() оноименное свойство. При неудаче генерируется исключение об ошибочности XML тега. Если для тега найден соответвующее свойство, то передаем дальнейшую обработку процедуре SetPropertyValue(), которая заданное свойство с именем TagName проинициализирует найденным значением TagValue.
Не забываем также передвигать указатель чтения данных TokenPtr по мере выборки данных.
{
Рекурсивная процедура загрузки объекта их текстового буфера с XML
Вызывается из:
Serialize()
Вход:
Component - компонент для конвертации
ComponentTagName - имя XML тега объекта
}procedure DeSerializeInternal(Component: TObject; const ComponentTagName: string);
var
BlockStart, BlockEnd, TagStart, TagEnd: PChar;
TagName, TagValue: PChar;
TypeInf: PTypeInfo;
TypeData: PTypeData;
PropIndex: integer;
AName: string;
PropList: PPropList;
NumProps: word;
{ Поиск у объекта свойства с заданным именем }function FindProperty(TagName: PChar): integer;
var
i: integer;
begin
Result := -1;
for i := 0 to NumProps-1 doif CompareText(PropList^[i]^.name, TagName) = 0 thenbegin
Result := i;
break;
end;
end;
procedure SkipSpaces(var TagEnd: PChar);
beginwhile (TagEnd[0] in [#0..#20]) do
inc(TagEnd);
end;
begin{ Playing with RTTI }
TypeInf := Component.ClassInfo;
AName := TypeInf^.name;
TypeData := GetTypeData(TypeInf);
NumProps := TypeData^.PropCount;
GetMem(PropList, NumProps*sizeof(pointer));
try
GetPropInfos(TypeInf, PropList);
{ ищем открывающий тег }
BlockStart := StrPos(TokenPtr, PChar("<" + ComponentTagName + ">"));
inc(BlockStart, length(ComponentTagName) + 2);
{ ищем закрывающий тег }
BlockEnd := StrPos(BlockStart, PChar("<<" + ComponentTagName + ">"));
TagEnd := BlockStart;
SkipSpaces(TagEnd);
{ XML парсер }while TagEnd dobegin
TagStart := StrPos(TagEnd, "<");
TagEnd := StrPos(TagStart, ">");
GetMem(TagName, TagEnd - TagStart + 1);
try{ TagName - имя тега }
StrLCopy(TagName, TagStart + 1, TagEnd - TagStart - 1);
TagEnd := StrPos(TagStart, PChar("" ? + TagNametry
{ TagValue - значение тега }
StrLCopy(TagValue, TagStart, TagEnd - TagStart);
{ поиск свойства, соответствующего тегу }
PropIndex := FindProperty(TagName);
if PropIndex = -1 thenraise Exception.Create(
"TglXMLSerializer.DeSerializeInternal: Uncknown property: " + TagName);
SetPropertyValue(Component, PropList^[PropIndex], TagValue);
inc(TagEnd, length("" ? + TagNamefinally
FreeMem(TagValue);
end;
finally
FreeMem(TagName);
end;
end;
finally
FreeMem(PropList, NumProps*sizeof(pointer));
end;
end;
Остается только код, который загрузит найденные данные в заданной свойство. Процедуре SetPropertyValue() передаются данные о соответствующем свойстве (PropInfo), которое на следует проинициализировать. Также процедура получает и текстовое значение, содержащееся в найденном теге.
В случае, если тип данные не является классовым типом, то, очевидно, текст Value следует просто загрузить в свойство. Это реализуется вызовом процедуры TypInfo.SetPropValue(). Последняя самостоятельно разберется, как корректно преобразовать тестовое значение в значение свойства в завистимости от его типа.
Если свойство имеет классовый тип, то его значение Value должно содержать XML код, описывающий свойства данного класса. В этом случае воспользуемся рекурсией и передадим обработку вышеприведенной процедуре DeSerializeInternal(). При этом передаем ей в качестве объекта ссылку на найденное свойство PropObject и его имя PropInfo^.Name.
Нам также необходимо озаботиться отдельной обработкой данных для таких классовых типов как списки TStrings и коллекции TCollection. Данные для списков мы загружаем из значения Value как CommaText. Тут все понятно. В сллучае же коллеций данные о элементах коллекции в XML документе содержаться в виде последовательных контейнерных тегов с именем типа элемента коллекци. Т.е., к примеру, <TMyCollection> ... </TMyCollection> <TMyCollection> ... </TMyCollection> <TMyCollection> ... </TMyCollection> и так далее. Внутри каждой пары тегов <TMyCollection> содержатся свойства объекта TMyCollection.
procedure SetPropertyValue(Component: TObject; PropInfo: PPropInfo; Value: PChar);
var
PropTypeInf: PTypeInfo;
PropObject: TObject;
CollectionItem: TCollectionItem;
sValue: string;
begin
PropTypeInf := PropInfo.PropType^;
case PropTypeInf^.Kind of
tkInteger, tkChar, tkEnumeration, tkFloat, tkString, tkSet,
tkWChar, tkLString, tkWString, tkVariant:
begin
sValue := StrPas(Value);
{ Для корректного преобразования парсером tkSet нужны угловые скобки }if PropTypeInf^.Kind = tkSet then
sValue := "[" + sValue + "]";
SetPropValue(Component, PropInfo^.name, sValue);
end;
tkClass:
begin
PropObject := GetObjectProp(Component, PropInfo);
if Assigned(PropObject)thenbegin{ Индивидуальный подход к некоторым классам }if (PropObject is TStrings) then{ Текстовые списки }
TStrings(PropObject).CommaText := Value
elseif (PropObject is TCollection) then{ Коллекции }beginwhile true do{ Заранее не известно число элементов в коллекции }begin
CollectionItem := (PropObject as TCollection).Add;
try
DeSerializeInternal(CollectionItem, CollectionItem.ClassName);
except{ Исключение, если очередной элемент не найден }
CollectionItem.Free;
break;
end;
end;
endelse{ Для остальных классов - рекурсивная обработка }
DeSerializeInternal(PropObject, PropInfo^.name);
end;
end;
end;
end;
К приведенному коду следует добавить еще ряд возможностей для более корректной реакции для обработки неверного XML кода. Также можно достаточно просто реализовать автоматическую генерацию DTD для любого класса Delphi. После этого можно собрать полноценный компонент, объединяющий в себе всю необходимую функциональность для XML сериализации.
Если Вас заинтересовала или понравилась информация по разработке на Delph - "Загрузка XML в объект", Вы можете поставить закладку в социальной сети или в своём блоге на данную страницу: Так же Вы можете задать вопрос по работе этого модуля или примера через форму обратной связи, в сообщение обязательно указывайте название или ссылку на статью!