XSLT like transformations in Erlang User Guide Mikael Karlsson 1.02002-10-25 First Draft 1.12003-02-05 Moved module xserl to xmerl application, renamed to xmerl_xs Erlang has similarities to XSLT since both languages have a functional programming approach. Using the xpath implementation in the existing xmerl application it is possible to write XSLT like transforms in Erlang. One can also combine the transformations with the erlang scripting possibility in the yaws webserver to implement "on the fly" html conversions of xml documents.
Terminology XML Extensible Markup Language XSLT Extensible Stylesheet Language: Transformations
Introduction XSLT stylesheets are often used when transforming XML documents, to other XML documents or (X)HTML for presentation. There are a number of brick-sized books written on the topic. XSLT contains quite many functions and learning them all may take some effort, which could be a reason why the author only has reached a basic level of understanding. This document assumes a basic level of understanding of XSLT. Since XSLT is based on a functional programming approach with pattern matching and recursion it is possible to write similar style sheets in Erlang. At least for basic transforms. XPath which is used in XSLT is also already implemented in the xmerl application written i Erlang. This document describes how to use the XPath implementation together with Erlangs pattern matching and a couple of functions to write XSLT like transforms. This approach is probably easier for an Erlanger but if you need to use real XSLT stylesheets in order to "comply to the standard" there is an adapter available to the Sablotron XSLT package which is written i C++. This document is written in the Simplified Docbook DTD which is a subset of the complete one and converted to xhtml using a stylesheet written in Erlang.
Tools
xmerl xmerl is a xml parser written in Erlang
xmerl_xpath XPath is in important part of XSLT and is implemented in xmerl
xmerl_xs xmerl_xs is a very small module acting as "syntactic sugar" for the XSLT lookalike transforms. It uses xmerl_xpath.
yaws Yaws, Yet Another Webserver, is a web server written in Erlang that support dynamic content generation using embedded scripts, also written in Erlang. Yaws is not needed to make the XSLT like transformations, but combining yaws and xmerl it is possible to do transformations of XML documents to HTML in realtime, when clients requests a web page. As an example I am able to edit this document using emacs with psgml tools, save the document and just do a reload in my browser to see the result. The parse/transform time is not visually different compared to loading any other document in the browser.
Transformations When xmerl_scan parses an xml string/file it returns a record of: Were content is a mixed list of yet other xmlElement records and/or xmlText (or other node types).
xmerl_xs functions Functions used: xslapply/2 function to make things look similar to xsl:apply-templates. value_of/1 Conatenates all text nodes within a tree. select/2 select(Str, E) extracts nodes from the XML tree using xmerl_xpath. built_in_rules/2 The default fallback behaviour, template funs should end with: template(E)->built_in_rules(fun template/1, E). Text is escaped using xmerl_lib:export_text/1 for "<", ">" and other relevant xml characters when exported. So the value_of/1 and built_in_rules/2 functions should be replaced when not exporting to xml or html.
Examples Using xslapply original XSLT:

]]>
becomes in Erlang: ["

", xslapply(fun template/1, E), "

"]; ]]>
Using value_of and select

]]>
becomes: ["

", value_of(select(".", E)), "

"]; ]]>
Simple xsl stylesheet A complete example with the XSLT sheet in the xmerl distribution. <xsl:value-of select="title"/>

NOTE:

]]>
Erlang version Erlang transformation of previous example: "". process_xml(Doc)-> template(Doc). template(E = #xmlElement{name='doc'})-> [ "<\?xml version=\"1.0\" encoding=\"iso-8859-1\"\?>", doctype(), "" "" "", value_of(select("title",E)), "" "" "", xslapply( fun template/1, E), "" "" ]; template(E = #xmlElement{ parents=[{'doc',_}|_], name='title'}) -> ["

", xslapply( fun template/1, E), "

"]; template(E = #xmlElement{ parents=[{'chapter',_}|_], name='title'}) -> ["

", xslapply( fun template/1, E), "

"]; template(E = #xmlElement{ parents=[{'section',_}|_], name='title'}) -> ["

", xslapply( fun template/1, E), "

"]; template(E = #xmlElement{ name='para'}) -> ["

", xslapply( fun template/1, E), "

"]; template(E = #xmlElement{ name='note'}) -> ["

" "NOTE: ", xslapply( fun template/1, E), "

"]; template(E = #xmlElement{ name='emph'}) -> ["", xslapply( fun template/1, E), ""]; template(E)-> built_in_rules( fun template/1, E). ]]>
It is important to end with a call to xmerl_xs:built_in_rules/2 if you want any text to be written in "push" transforms. That are the ones using a lot xslapply( fun template/1, E ) instead of value_of(select("xpath",E)), which is pull...
The largest example is the stylesheet to transform this document from the Simplified Docbook XML format to xhtml. The source file is sdocbook2xhtml.erl.
Tips and tricks
for-each The function for-each is quite common in XSLT stylesheets. It can often be rewritten and replaced by select/1. Since select/1 returns a list of #xmlElements and xslapply/2 traverses them it is more or less the same as to loop over all the elements.
position() The XSLT position() and #xmlElement.pos are not the same. One has to make an own position in Erlang. Counting positions

  
]]>
Can be written as {Lines,LineNo} = lists:mapfoldl(fun template_pos/2, 1, select("line", E)), ["

", Lines, "

"]. template_pos(E = #xmlElement{name='line'}, P) -> {[indent_line(P rem 2), value_of(E#xmlElement.content), "
"], P + 1 }. indent_line(0)->"  "; indent_line(_)->"". ]]>
Global tree awareness In XSLT you have "root" access to the top of the tree with XPath, even though you are somewhere deep in your tree. The xslapply/2 function only carries back the child part of the tree to the template fun. But it is quite easy to write template funs that handles both the child and top tree. Passing the root tree The following example piece will prepend the article title to any section title ["

", value_of(select("title", ETop))," - ", xslapply( fun(A) -> template(A, ETop) end, E), "

"]; ]]>
Utility functions The module xmerl_xs contains the functions mapxml/2, foldxml/3 and mapfoldxml/3 to traverse #xmlElement trees. They can be used in order to build cross-references, see sdocbook2xhtml.erl for instance where foldxml/3 and mapfoldxml/3 are used to number chapters, examples and figures and to build the Table of contents for the document.
Future enhancements More wish- than task-list at the moment. More stylesheets On the fly exports to PDF for printing and also more "polished" presentations.
References XML source file for this document. Erlang style sheet used for this document. (Simplified Docbook DTD). Open Source Erlang