Technical Articles
How to process dynamic XSLT in ABAP
In the below post you will find a basic example how to use cl_xslt_processor library. However it is really poorly documented, so I decided to dive deeper, test it a little bit and let you know how to use it in more details.
Backstory
Lately I have encountered a small problem. We wanted to develop an interface, which would consume a simple (not really) web service. Easy, right? You just save necessary SOAP envelopes, set headers and send HTTP request.
Yes, but we wanted to make this development reusable and scalable in order to consume not yet known web services in the future. What if we could modify request body (XML) from the outside without modifying our code? Set descriptions and other fields in the request any way we wanted, using data available in the program?
Generating XML from modifable XSLT would allow us to achieve everythinfg described above.
I started digging and found this trace on the web – https://archive.sap.com/discussions/thread/947687 – trace of cl_xslt_processor class.
In the above post you will find a basic example how to use this library. However it is really poorly documented, so I decided to dive deeper, test it a little bit and let you know how to use it in more details.
Implementation
When you look at the class definition you will see two main groups of methods – set_source* and set_result* :
Set_source* sets incoming message to be transformed, while set_result* points to the result object after transformation. In this example I will be using objects of well known iXML library.
Source XML:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="example.xsl"?>
<Article>
<Title>My Article</Title>
<Authors>
<Author>Mr. Foo</Author>
<Author>Mr. Bar</Author>
</Authors>
<Body>This is my article text.</Body>
</Article>
XSLT (examples taken from Mozilla webservice):
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
Article - <xsl:value-of select="/Article/Title"/>
Authors: <xsl:apply-templates select="/Article/Authors/Author"/>
</xsl:template>
<xsl:template match="Author">
- <xsl:value-of select="." />
</xsl:template>
</xsl:stylesheet>
First we need to create iXML objects:
DATA(lo_xml) = cl_ixml=>create( ).
DATA(lo_source_document) = lo_xml->create_document( ).
DATA(lo_result_document) = lo_xml->create_document( ).
DATA(lo_stream_factory) = lo_xml->create_stream_factory( ).
DATA(lo_xml_istream) = lo_stream_factory->create_istream_string( xml_string ).
DATA(lo_xslt_istream) = lo_stream_factory->create_istream_string( xslt ).
DATA(lo_parser) = lo_xml->create_parser( document = lo_source_document
istream = lo_xml_istream
stream_factory = lo_stream_factory ).
lo_parser->parse( ).
As you can see lo_source_document represents source XML under variable xml_string, while lo_result_document is an empty document. lo_xslt_stream will be used as a source stream in cl_xslt_processor.
DATA(lo_xslt_processor) = NEW cl_xslt_processor( ).
TRY.
lo_xslt_processor->set_source_node( lo_source_document ).
lo_xslt_processor->set_result_document( lo_result_document ).
lo_xslt_processor->set_source_stream( EXPORTING stream = lo_xslt_istream
CHANGING p = lv_progname_string ).
lo_xslt_processor->run( space ).
CATCH cx_xslt_exception.
ENDTRY.
We set source and result document, XSLT istream from prevoius step and run.
Quick way to display:
DATA(lo_cl_xml) = NEW cl_xml_document( ).
lo_cl_xml->create_with_dom( lo_result_document ).
lo_cl_xml->render_2_string(
EXPORTING
pretty_print = 'X'
IMPORTING
stream = DATA(lv_string)
).
Result
Check the content of final string:
Hooray!
Below you can find the whole code and test it on your own:
DATA: lv_progname_string TYPE string VALUE abap_true.
DATA(xml_string) = |<?xml version="1.0"?>| &&
|<?xml-stylesheet type="text/xsl" href="example.xsl"?>| &&
|<Article>| &&
| <Title>My Article</Title>| &&
| <Authors>| &&
| <Author>Mr. Foo</Author>| &&
| <Author>Mr. Bar</Author>| &&
| </Authors>| &&
| <Body>This is my article text.</Body>| &&
|</Article>|.
DATA(xslt) = |<?xml version="1.0"?>| &&
|<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">| &&
| <xsl:output method="text"/>| &&
| <xsl:template match="/">| &&
| Article - <xsl:value-of select="/Article/Title"/>| &&
| Authors: <xsl:apply-templates select="/Article/Authors/Author"/>| &&
| </xsl:template>| &&
| <xsl:template match="Author">| &&
| - <xsl:value-of select="." />| &&
| </xsl:template>| &&
|</xsl:stylesheet>|.
DATA(lo_xml) = cl_ixml=>create( ).
DATA(lo_source_document) = lo_xml->create_document( ).
DATA(lo_result_document) = lo_xml->create_document( ).
DATA(lo_stream_factory) = lo_xml->create_stream_factory( ).
DATA(lo_xml_istream) = lo_stream_factory->create_istream_string( xml_string ).
DATA(lo_xslt_istream) = lo_stream_factory->create_istream_string( xslt ).
DATA(lo_parser) = lo_xml->create_parser( document = lo_source_document
istream = lo_xml_istream
stream_factory = lo_stream_factory ).
lo_parser->parse( ).
DATA(lo_xslt_processor) = NEW cl_xslt_processor( ).
TRY.
lo_xslt_processor->set_source_node( lo_source_document ).
lo_xslt_processor->set_result_document( lo_result_document ).
lo_xslt_processor->set_source_stream( EXPORTING stream = lo_xslt_istream
CHANGING p = lv_progname_string ).
lo_xslt_processor->run( space ).
CATCH cx_xslt_exception.
ENDTRY.
DATA(lo_cl_xml) = NEW cl_xml_document( ).
lo_cl_xml->create_with_dom( lo_result_document ).
lo_cl_xml->display( ).
lo_cl_xml->render_2_string(
EXPORTING
pretty_print = 'X'
IMPORTING
stream = DATA(lv_string)
).
WRITE lv_string.
What is your experience with XSLT in ABAP?
I use only XSLT when there's a very complex transformation. For most of cases, I use ST as I had measured a 10-times better performance, a few years ago. I also prefer using CALL TRANSFORMATION, usually no need of all this iXML stuff. There's also an intermediate sXML library which is useful for reading or generating JSON very simply for instance.
great article!
Excellent Article!!!