Показаны сообщения с ярлыком xml. Показать все сообщения
Показаны сообщения с ярлыком xml. Показать все сообщения

вторник, 18 сентября 2018 г.

Парсинг XML с помощью SimpleTransformation с пропуском тегов.

Итак, допустим, есть такой XML:
<root>
<values>
<material>1</material>
<prod>XXXX</prod>
<foo>bar</foo>
<values>
</root>
view raw data.xml hosted with ❤ by GitHub
И, допустим, нас интересует только значение тега material (которого, кстати, может и не быть). Напишем такую версию simple transformation:

<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Применение данной трансформации вызовет исключительную ситуацию, так как в XML нам встретиться тег prod и тег foo, которые мы не предусмотрели в трансформации.

Конечно, их можно добавить в трансформацию и никак не обрабатывать их значения. Но таких ненужных нам тегов может быть много и прописывать их все это некомильфо. Кроме того, могут быть ситуации, когда XML описан не четко и подразумевает, что внутри values могут появляться не известные нам на этапе разработки теги.

Как пропускать все что нам не нужно или мы не знаем что это? Для этого нужно в конец tt:group добавить tt:skip следующим образом:
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="*">
<tt:skip count="1"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Здесь мы "говорим", что если нам встречается тег material, то мы берем его значение и помещаем в ROOT.MATERIAL, иначе мы пропускаем 1 тег.
Тег tt:group работает как цикл + switch/case - берет на каждом шаге следующий тег в values и сравнивает по условиям tt:d-cond, если условие проходит - выполняется то, что внутри tt:d-cond, и происходит следующая итерация "цикла". Таким образом, по примеру выше, мы найдем на первой итерации тег material, запишем его значение в root.material, найдем на следующей итерации prod и пропустим его, далее найдем на следующей итерации foo и также пропустим его (и аналогично для других тегов если они будут) и выйдем из values. Тег tt:skip должен быть в конце tt:group.

Но с данной реализацией есть одна неприятность. Толи это баг в реализации SimpleTransfomation, толи это by design. Заключается она в следующем:

Если в исходном XML будет только тег material, это также вызовет ошибку трансформации. Отладка данного случая показывает, что обработав тег material, парсер все равно пытается выполнить tt:skip хотя бы один раз. Делает он это для следующего тега, которым будет закрывающий тег </values>. В итоге правило для самого values не найдет своего закрывающего тега и будет выброшено исключение. Но если в XML есть хотя бы один тег, который нам надо пропустить, то tt:skip сработает 1 раз и далее не будет пытаться пропустить </values>.

Обойти это можно, явно добавив еще один фиктивный тег в конец набора тегов в values в исходном XML. Сделать это можно перед вызовом трансформации например так (XML находится в переменной gv_xml):

REPLACE ALL OCCURRENCES OF '</values>' IN gv_xml WITH '<dummyEnd /></values>' IGNORING CASE.'

а в трансформацию явно добавить обработку тега dummyEnd:
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:template>
<root>
<values>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="?">
<dummyEnd />
</tt:d-cond>
<tt:d-cond frq="*">
<tt:skip count="1"/>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Не понятно почему, но данный подход срабатывает, и после обработки dummyEnd парсер не пытается выполнить tt:skip.

Если же работать перестанет, то есть более сложный, но более логичный вариант трансформации (в XML также все еще нужно добавлять фиктивный тег):
<?sap.transform simple?>
<tt:transform xmlns:tt="http://www.sap.com/transformation-templates">
<tt:root name="ROOT"/>
<tt:variable name="skip"/>
<tt:template>
<root>
<values>
<tt:assign to-var="skip" val="0"/>
<tt:group>
<tt:d-cond frq="?">
<material tt:value-ref="material"/>
</tt:d-cond>
<tt:d-cond frq="?">
<dummyEnd>
<tt:assign to-var="skip" val="1"/>
</dummyEnd>
</tt:d-cond>
<tt:d-cond frq="*">
<tt:cond-var check="skip=0">
<tt:skip count="1"/>
</tt:cond-var>
</tt:d-cond>
</tt:group>
</values>
</root>
</tt:template>
</tt:transform>
view raw transform.xslt hosted with ❤ by GitHub
Здесь мы заводим переменную skip. Далее при начале обработки тега values мы записываем в переменную skip значение 0. В условии tt:cond-var check="skip=0" мы проверяем, что skip равно 0 и, если это так, то пропускаем один тег с помощью tt:skip. Если мы встречаем тег dummyEnd, то записываем в переменную skip значение 1. Таким образом, после последнего тега в values (а мы позаботились чтобы им был dummyEnd), больше не требуется пропускать теги, tt:skip не будет выполнен, и будет обработан закрывающий </values>