[Из песочницы] XSLT преобразование внутренней таблицы в ABAP, имеющей поле типа «generic reference»

Пролог О чем заметка? Как из внутренней таблицы, строка которой содержит ссылку на неизвестный (обобщенный) тип (REF TO DATA), которая, по факту, хранит ссылку на такую же таблицу, получить XML заданного формата. При этом, число уровней вложенности изначально неизвестно.Зачем это нужно? Мне это понадобилось при выгрузке данных в различные форматы XML-файлов MS Office без использования OLE.Для кого эта заметка? Для программистов на ABAP.Необходимый уровень знания: знать, что такое reference type, generic type, XML; слышать, что существует такая вещь как XSLT.Как же с этим бороться? Задача Есть дерево. Дерево хранится во внутренней таблице. Тип таблицы определен следующим образом: TYPES : BEGIN OF tdeep_struct , name TYPE string , rf_child_list TYPE REF TO data «та самая обобщенная сылка , END OF tdeep_struct

, t_deep_struct TYPE STANDARD TABLE OF tdeep_struct WITH NON-UNIQUE DEFAULT KEY . Т.е. структура строки таблицы в поле rf_child_list содержит ссылку на обобщенный тип (generic type).

Из внутренней таблицы такой структуры, нужно получить XML следующего формата:

Имя первой записи Имя следующей записи При этом расположение узлов исходного дерева в итоговом XML должно соответствовать нисходящему обходу дерева (Т.е. начинаем просмотр записей внутренней таблицы сверху. Выводим содержимое первой записи, если у нее есть зависимые записи в rf_child_list — то начинаем выводить их с первой записи. Если у нее есть зависимые — то следом выводим их и т.д.).

Тестовый пример Допустим, у нас во внутренней таблице сохранено дерево следующего вида (перечислены значения поля Name): GrandParent1 Child1 Child2 GrandChild1 GrandParent2 По условиям задачи мы должны получить из этих данных вот такой XML.Итоговый XML для тестового примера GrandParent1 Child1 Child2 GrandChild1 GrandParent2 Решение Начнем с простого: ABAP-код, что же может быть проще? Определим и заполним тестовыми данными внутреннюю таблицу DATA : gt_tree TYPE t_deep_struct «таблица дерева , gtree TYPE tdeep_struct «рабочая область записи первого уровня , gchild TYPE tdeep_struct «рабочая область записи второго уровня , ggrandchild TYPE tdeep_struct «рабочая область записи третьего уровня .

FIELD-SYMBOLS : TYPE t_deep_struct «внутренняя таблица записей второго уровня , TYPE t_deep_struct «внутренняя таблица записей третьего уровня .

START-OF-SELECTION.

gtree-name = 'GrandParent1'.

CREATE DATA gtree-rf_child_list TYPE t_deep_struct.

ASSIGN gtree-rf_child_list→* TO .

gchild-name = 'Child1'. APPEND gchild TO .

gchild-name = 'Child2'.

CREATE DATA gchild-rf_child_list TYPE t_deep_struct. ASSIGN gchild-rf_child_list→* TO . ggrandchild-name = 'GrandChild1'. APPEND ggrandchild TO .

APPEND gchild TO .

APPEND gtree TO gt_tree.

CLEAR gtree.

gtree-name = 'GrandParent2'. APPEND gtree TO gt_tree. Решая задачу я подумал, что надо бы использовать XSLT-преобразование. Так как тэг у этой заметки я поставил «XSLT для начинающих», то надо упомянуть, что же это такое. Если совсем на пальцах, то это такой язык, который позволяет из одного XML получить другой XML. При этом сам XSLT является подмножеством XML. Стоп! А причем тут внутренние таблицы, если он преобразует XML? А это такая особенность реализации XSLT в SAP. Посмотрим подробнее.

За выполнение трансформации в ABAP отвечает оператор CALL TRANSFORMATION. У него много параметром и много вариантов работы.Входные данные требующие преобразования будем передавать, указав SOURCE имя_параметра = имя_внутренней_таблицы.Результат можно получить указав RESULT XML объектXML/внутренняя таблица/строка/объект потока. Для простоты, получать результат будем в строку.

Результат трансформации отобразим на экране стандартными средствами объекта CL_XML_DOCUMENT.Получаем вот такой код:

CALL TRANSFORMATION zedu_test_xslt_deep_transform2 SOURCE table = gt_tree RESULT XML gxml_str.

go_xml_doc→parse_string (gxml_str).

go_xml_doc→display (). Ах да… я же отвлекся! Вопрос-то был: и причем тут трансформация, если вообще-то она для преобразовании одного XML в другой?

Немного rtfm Начнем с того, что в SAP трансформации используются для сериализации и десериализации данных. Т.е., как из какого-то объекта данных (структуры, внутренней таблицы, объекта) получить представление в виде последовательности битов и наоборот. У нас как раз задача сериализации: на входе вложенная структура (внутренняя таблица ABAP), а на выходе — XML (текстовое представление).XSLT в SAP бывают 2-ух видов: обычный XSLT и simple transformation (ST). SAP показалось мало XSLT и он реализовал свое подмножество XSLT, которое назвал simple transformation. Нам оно не подходит, т.к. не умеет работать с полями generic type в входных параметрах.Остается чистый XSLT.SAP, при выполнении сериализации с применением XSLT, приводит входные параметры трансформации к виду asXML (canonical XML representation). В процессе этого преобразования, по определенным правилам, происходит конверсия типов ABAP, это касается и generic type (Явно получить каноническое представление какой либо переменной можно использовав стандартное для ABAP XSLT преобразование с зарезервированным именем ID).Картинка из хелпа ABAP, объясняющая преобразования данных при трансформацияхНас интересует направление SerializationДля моего примера каноническое XML представление выглядит следующим образом:

GrandParent1 GrandParent2
Child1 Child2 GrandChild1 И как с этим жить дальше? При получении канонического представления XML для внутренней таблицы, сама таблица преобразуется в элемент , внутри которого для каждой строки будет существовать элемент . Каждое поле будет преобразовано в элемент с именем равным имени поля. Т.к. в мою структуру входит поле RF_CHILD_LIST, являющееся ссылкой на обобщенный тип DATA (TYPE REF TO DATA), то содержимое соответствующего элемента будет пустым, а в атрибуте элемента href будет храниться ссылка на содержимое, находящееся далее по документу.Например, для первой строки значение , где символ # — признак ссылки по правилам формирования ссылок XLink, а d1 — идентификатор ссылки. Само содержимое будет находиться в специальном разделе канонического XML-документа: asx: heap в элементе, имеющем атрибут id равный d1. В нашем примере prg: T_DEEP_STRUCT id=«d1» … >. Так как у нас рекурсивная структура, то дальнейшая вложенность элементов в каноническом представлении организована аналогично.Т.е., как выглядит исходный XML документ для преобразования, мы разобрались.А XSLT-то где? Теперь нужно реализовать собственно XSLT-преобразование.В SAP для создания трансформаций используется транзакция STRANS.9e21a462a5794b7b820619abc4edb1ec.jpgВводим название трансформации, нажимаем «Создать»(F5).f251a9ff31c7413f96bf83dc1ee3a4e6.jpgЕсли нужно — изменяем название, указываем краткое описание и вид трансформации (в нашем случае — XSLT). Нажимаем Enter.При редактировании трансформации работают контекстные подсказки по элементам XSLT, а так же есть возможность использовать библиотеку тэгов. Кроме того, есть инструмент тестирования с загрузкой данных из файла, а так же — отладчик преобразования.

Использовать структурную печать в редакторе следует с осторожностью, т.к., в ряде случаев, редактор будет вставлять не нужные символы перевода строки (это может быть критично, если в итоге мы, например, хотим получить html документ).

При создании нового документа, система сразу же вставит шаблон пустого документа, т.е. мы его будем менять. Шаблон будет выглядеть следующим образом

Где:

xmlns: xsl означает, что мы будем использовать пространство имен xsl. Т.е. при использовании элементов их этого пространства имен, элементы будут иметь в начале префикс xsl: (например ) xmlns: sap означает, что мы будем использовать пространство имен sap xsl: transform указание, что это у нас XSLT-преобразование убирает лишние пробелы при преобразовании определяет шаблон преобразования, который применяется к корневому элементу исходного XML документа XSLT-трансформация применяет преобразования указанные внутри шаблонов (xsl: template) к тем узлам исходного XML-документа, которые соответствуют выражению в атрибуте match. Значение атрибута match определяется с помощью специального языка XPath. В рамках этого языка, корневой элемент определяется как »/», доступ к элементу нашего исходного XML-документа TABLE будет определен как »/asx: abap/asx: values/TABLE» (т.е. перечисляются все узлы XML-документы от корневого (asx: abap) до нашего узла (TABLE)).

Для начала, добавляем корневой элемент нашего выходного XML-документа List. Шаблон примет вид:

Нам необходимо перебрать в цикле все элементы item внутри элемента TABLE нашего исходного XML документа, это делается с помощью команды , где в атрибуте select указывается XPath путь до элементов, по которым будет идти итерация. Внутри цикла будем выводить наш элемент MyItem, в котором выведем элемент Name со значением NAME соответствующего элемента исходного документа, по которому идет итерация. Таким образом, шаблон преобразования будет иметь теперь следующий вид:

Значение из элемента исходного документа выводится командой , в атрибуте select которого указывается XPath-путь до элемента, из которого нужно взять значение. Обратим внимание, что путь в xsl: value-of указан несколько иначе, чем в xsl: for-each. Разница в том, что в xsl: for-each указан абсолютный путь, начиная от корневого элемента, а в xsl: value-of — относительный от вышестоящего элемента (т.е. пути в команде xsl: for-each).Таким образом, реализовав данный шаблон, мы выведем только первый уровень рекурсивной структуры исходного документа.Кроме того, при использовании такого шаблона у нас будут выводиться лишние пространства имен asx, sap, prg (XSLT их не отключает по умолчанию). Что бы исключить их из выходного XML, изменим xsl: transform следующим образом:

Т.е. мы указали используемые пространства имен, а потом атрибутом exclude-result-prefixes перечислили те, которых не должно быть в выходном XML.Теперь нужно добавить нисходящий обход тех самых данных, ссылка на которые лежит в элементе RF_CHILD_LIST. Для этого мы будем использовать еще один шаблон. Фактически шаблон — это как подпрограмма.Предположим, что нам известен id соответствующий списку ссылок. Обозначим его как ChilListID. Это будет входной параметр шаблона.Вот определение шаблона и его параметра:

Т.е. мы определили шаблон с именем OutChilds и параметром ChilListID. При этом, шаблон может обрабатывать только часть исходного XML-документа, которая соответствует поддереву /asx: abap/asx: heap.

Что нам нужно сделать в этом шаблоне? Перебрать все элементы item внутри элемента prg: T_DEEP_STRUCT с id равным входному параметру ChilListID. Делаем это с помощью уже знакомого нам xsl: for-each.Сперва отыщем prg: T_DEEP_STRUCT с id равным входному параметру ChilListID: Здесь XPath определяет, что итерация должна идти по тем элементам prg: T_DEEP_STRUCT, у которых аттрибут id равен параметру ChilListID.Внутри данного цикла сделаем цикл по всем item:

Однако, можно сделать и иначе в рамках нашей задачи, без вложенных циклов, объединив их в один, XSLT такое позволяет. Выглядеть это будет вот так:

Выведем элемент MyItem и его Name. Получаем следующий шаблон обработки:

Мы вывели один уровень подчиненных узлов. Теперь нужно реализовать рекурсию: вызвать этот шаблон внутри него самого.Вызов шаблона выполняется командой xsl: apply-templates, с указанием в атрибуте select к какой части дерева исходногоXML у нас будет применен этот шаблон.Кроме того, не забываем, что нам еще нужно передать параметр ChilListID. Запись, которая ссылается на список дочерних записей, имеет в элементе RF_CHILD_LIST атрибут href, значение которого равно #id_списка_дочерних_записей. Т.е. нам нужно получить атрибут href и вырезать из него подстроку, начиная со второго символа. Это выполняется с помощью XPath следующим образом: substring (RF_CHILD_LIST/@href,2). Т.е. получается вот такой вызов шаблона:

В итоге, получаем шаблон обработки дочерних записей:

Теперь вставим, по аналогии, вызов этого шаблона в шаблон, работающий для корневого узла.

В итоге, полный текст преобразования примет следующий вид:

Полный текст ABAP-программы REPORT zedu_test_xslt_deep_transform.

*определим тип структуры имеющей поле типа «ссылка на обобщенный тип» TYPES : BEGIN OF tdeep_struct , name TYPE string , rf_child_list TYPE REF TO data , END OF tdeep_struct

*табличный тип для структуры имеющей поле типа «ссылка на обобщенный тип» , t_deep_struct TYPE STANDARD TABLE OF tdeep_struct WITH NON-UNIQUE DEFAULT KEY .

DATA : gt_tree TYPE t_deep_struct «таблица дерева , gtree TYPE tdeep_struct «рабочая область верхнего уровня для таблицы дерева , gchild TYPE tdeep_struct «рабочая область второго уровня для таблицы дерева , ggrandchild TYPE tdeep_struct «рабочая область третьего уровня для таблицы дерева , gxml_str TYPE string «строка результирующего XML , go_xml_doc TYPE REF TO cl_xml_document «объект для отображения результирующего XML .

FIELD-SYMBOLS : TYPE t_deep_struct «таблица записей второго уровня , TYPE t_deep_struct «таблица записей третьего уровня .

START-OF-SELECTION. *заполнение таблицы тестовыми данными gtree-name = 'GrandParent1'.

CREATE DATA gtree-rf_child_list TYPE t_deep_struct.

ASSIGN gtree-rf_child_list→* TO .

gchild-name = 'Child1'. APPEND gchild TO .

gchild-name = 'Child2'. CREATE DATA gchild-rf_child_list TYPE t_deep_struct. ASSIGN gchild-rf_child_list→* TO .

ggrandchild-name = 'GrandChild1'. APPEND ggrandchild TO .

APPEND gchild TO .

APPEND gtree TO gt_tree.

CLEAR gtree.

gtree-name = 'GrandParent2'. APPEND gtree TO gt_tree.

*вызов XSLT-трансформации CALL TRANSFORMATION zedu_test_xslt_deep_transform2 SOURCE table = gt_tree RESULT XML gxml_str.

*покажем стандартными средствами полученный XML CREATE OBJECT go_xml_doc. go_xml_doc→parse_string (gxml_str). go_xml_doc→display (). Заключение Правда ведь, не так уж и страшен XSLT для абапера? Использованные материалы Справка ABAP MSDN: справочник по XSLT XPath tutorial Спецификация XSLT

© Habrahabr.ru