XML building using lamdas

Feb 17, 2023


Building HTML Programmatically with Curried Lambdas

When you need to build XML or HTML programmatically, there’s no shortage of
libraries available, such as XmlBuilder, Nokogiri, or REXML. But have you
ever thought of building it yourself? Here, we’ll explore how to use curried
lambdas for this purpose.

A basic HTML element is typically represented as:

<tag attributes>children</tag>

We can convert this structure into a curried lambda in Ruby:

node = -> tag, attrs, children { "<#{tag} #{attrs}>#{children.join}</#{tag}>" }.curry

In this representation, the children are an array of nodes. For our current
approach, attributes are represented as strings.

The beauty of currying lambdas is evident as it allows us to “pre-initialize”
them. We can then bind these lambdas to variables as shown below:

div = node.("div")
table = node.("table")
thead = node.("thead")
tbody = node.("tbody")
tr = node.("tr")
th = node.("th")
td = node.("td")

Now, leveraging these foundational building blocks, constructing an HTML
structure, like a table, becomes a breeze:

page = table.('class="table"').(
  [thead.("").(
    [tr.("").(
      [th.("").(["Name"]),
       th.("").(["Age"])]
    )]

  ),
   tbody.("").(
    [tr.("").([
      td.("").(["Martin"]),
      td.("").(["34"]),
    ])]
  )])

View XML/HTML code

If you print the page source you will see that it’s only on one line. To view it as a tree, you can
use this pretty_xml lambda:

require 'nokogiri'

pretty_xml = ->(xml_str) do
  doc = Nokogiri::XML(xml_str) { |config| config.default_xml.noblanks }
  doc.to_xml(indent: 2)
end
pretty_html = ->str { Nokogiri::HTML(str).tap { |a| a.to_html(indent: 2) } }


## Example usage
puts pretty_xml.(page)

It will give something like this

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
  <body>
    <table class="table">
      <thead>
        <tr>
          <th>Name</th>
          <th>Age</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>Martin</td>
          <td>34</td>
        </tr>
      </tbody>
    </table>
  </body>
</html>

To view in MacOS Browser

browse = -> page {
  file = "/tmp/my_page.html"
  File.write(file, page)
  system("open '#{file}'")
}
browse.(page)

This will open up your browser to visualize your page.


Conclusion

Using curried lambdas in Ruby not only provides an elegant and precise way to
articulate functionality but also offers a remarkably concise approach to
creating Domain Specific Languages (DSLs). The ability of lambdas to undergo
partial application allows for flexibility across the codebase, paving the way
for efficient and streamlined DSL creation.