Simple DSL for creating html in Python



In Clojure world we have hiccup for creating html:

[ :div.top [ :h1 " Hello world ] [ :p hello-text ]]

In JS world we have JSX (it’s not internal DSL, but it’s relevant):

var html = ( < div className = " top " > < h1 > Hello world < /h1 > < p > { helloText } < /p > < /div > );

But in Python we don’t have similar DSL (upd: actually we have: lxml.E, pyxl, Dominate and The DOM), and isn’t it be cool (actually it isn’t, I don’t recommend to do something like this, it’s just an experiment) to write something like this:

h . div ( klass = 'top' )[ h . h1 [ "Hello word" ], h . p [ hello_text ]]

Let’s start with simplest part, implement ability to call h.p and h.div , for this I’ll use magic of metaclasses and __getattr__ :

class hBase ( type ): def __getattr__ ( cls , name ): return cls ( name ) class h ( metaclass = hBase ): def __init__ ( self , name ): self . _name = name def __str__ ( self ): return '<{name}></{name}>' . format ( name = self . _name ) def __repr__ ( self ): return str ( self ) In [ 3 ]: h . div Out [ 3 ]: < div ></ div >

It’s very simple, now is the time to add ability to define childs for html element with h.div[h.h2, h.p] , magic of __getitem__ will help me:

class hBase ( type ): def __getattr__ ( cls , name ): return cls ( name ) class h ( metaclass = hBase ): def __init__ ( self , name , childs = None ): self . _name = name self . _childs = childs def __getitem__ ( self , childs ): if not hasattr ( childs , '__iter__' ): childs = [ childs ] return type ( self )( self . _name , childs ) def _format_childs ( self ): if self . _childs is None : return '' if isinstance ( self . _childs , str ): return self . _childs else : return '

' . join ( map ( str , self . _childs )) def __str__ ( self ): return '<{name}>{childs}</{name}>' . format ( name = self . _name , childs = self . _format_childs ()) def __repr__ ( self ): return str ( self ) In [ 7 ]: h . div [ h . h2 [ 'Hello world' ], h . p [ 'Just text.' ]] Out [ 7 ]: < div >< h2 > Hello world </ h2 > < p > Just text . </ p ></ div >

Cool, it works! So now let’s add ability to define attributes with h.div(id="my-id") , but before I need to notice that in python we not allowed to use class as a name of argument, so I’ll use klass instead. So here I’ll use magic of __call__ :

class hBase ( type ): def __getattr__ ( cls , name ): return cls ( name ) class h ( metaclass = hBase ): def __init__ ( self , name , childs = None , attrs = None ): self . _name = name self . _childs = childs self . _attrs = attrs def __getitem__ ( self , childs ): if not hasattr ( childs , '__iter__' ): childs = [ childs ] return type ( self )( self . _name , childs , self . _attrs ) def __call__ ( self , ** attrs ): return type ( self )( self . _name , self . _childs , attrs ) def _format_attr ( self , name , val ): if name == 'klass' : name = 'class' return '{}="{}"' . format ( name , str ( val ). replace ( '"' , ' \" ' )) def _format_attrs ( self ): if self . _attrs : return ' ' + ' ' . join ([ self . _format_attr ( name , val ) for name , val in self . _attrs . items ()]) else : return '' def _format_childs ( self ): if self . _childs is None : return '' if isinstance ( self . _childs , str ): return self . _childs else : return '

' . join ( map ( str , self . _childs )) def __str__ ( self ): return '<{name}{attrs}>{childs}</{name}>' . format ( name = self . _name , attrs = self . _format_attrs (), childs = self . _format_childs ()) def __repr__ ( self ): return str ( self ) In [ 19 ]: hello_text = 'Hi!' In [ 20 ]: h . div ( klass = 'top' )[ h . h1 [ "Hello word" ], h . p [ hello_text ]] Out [ 20 ]: < div class = "top" >< h1 > Hello word </ h1 > < p > Hi ! </ p ></ div >

Yep, it’s working, and it’s a simple DSL/template language just in 44 lines of code, thanks to Python magic methods. It can be used in more complex situations, for example – blog page:

from collections import namedtuple BlogPost = namedtuple ( 'BlogPost' , ( 'title' , 'text' )) posts = [ BlogPost ( 'Title {}' . format ( n ), 'Text {}' . format ( n )) for n in range ( 5 )] In [ 30 ]: h . body [ h . div ( klass = 'header' )[ h . h1 [ 'Web page' ], h . img ( klass = 'logo' , src = 'logo.png' )], h . div ( klass = 'posts' )[( h . article [ h . h2 ( klass = 'title' )[ post . title ], post . text ] for post in posts )]] Out [ 30 ]: < body >< div class = "header" >< h1 > Web page </ h1 > < img class = "logo" src = "logo.png" ></ img ></ div > < div class = "posts" >< article >< h2 class = "title" > Title 0 </ h2 > Text 0 </ article > < article >< h2 class = "title" > Title 1 </ h2 > Text 1 </ article > < article >< h2 class = "title" > Title 2 </ h2 > Text 2 </ article > < article >< h2 class = "title" > Title 3 </ h2 > Text 3 </ article > < article >< h2 class = "title" > Title 4 </ h2 > Text 4 </ article ></ div ></ body >

And after that little experiment I have to say that everything is a LISP if you’re brave enough =)