Remarkuple - XML/HTML tag generator/factory documentation

Design notes

Purpose is to create a simple html generator for Python. Few other generators have been available since decade but they all seems to have small caveats. However combining features I've managed to create a library that fits better to my own projects.

Problems:

  1. Some libraries use awkward CAPITAL letters, convention derived from early age of internet. I prefer small letters on tag and attribute names as specified on xhtml standard.
  2. Reserved words in Python language limits using some tag and tag attribute names. This has been tackled by allowing usage of CAPITAL letters on helper interface, but on the background letters are forced to lowercase. This however can be passed by using tag.setName and tag.setAttribute -methods. In this manner you can set any html standard tag and attribute name on the document like <my-tag dc:attr="" />
  3. I don't want to limit tags to html4 tag names only, but allow practically any tag name. See above.
  4. Sometimes content is transformed to html entities, which should not occur until very end of the business logic.

Other requirements:

As simple implementation as possible, no need for complicated page generation methods, just basic functionality. Extending tags for structures like table, lists and svg graphics. Nesting tags and giving attribute names should be clean and intuitive. Pythonic.

Base class


In [1]:
class TAG(object):
    def __init__(self, *args, **kw):
        pass
    def getAttribute(self, key):
        pass
    def setAttribute(self, key, value):
        pass
    def addContent(self, item):
        pass

Helper


In [2]:
class htmlHelper(object):
    def create(self, name):
        pass
    def __getattr__(self, tag):
        pass

Usage


In [3]:
from remarkuple import helper as h, concat

In [4]:
# introducing the main flow of the nesting tags
print h.html(h.head(h.title("Simple html document")), h.body("Content"))


<html><head><title>Simple html document</title></head><body>Content</body></html>

In [5]:
# a tag without content will be output as a short tag form
print h.br()


<br/>

In [6]:
# if you pass empty string on tag content, closing tag will be generated
print h.script('')


<script></script>

In [7]:
# content can be a string, a numeric or other tag elements
print h.h1("Header ", 1, h.span(".1"))


<h1>Header 1<span>.1</span></h1>

In [8]:
# providing other content can yield unexpected results because all will be string normalized
print h.div([0,1], {'k': h.b()})


<div>[0, 1]{'k': <remarkuple.main.b object at 0x102fa6610>}</div>

In [9]:
# content can be callable
print h.p(h.br)


<p><br/></p>

In [10]:
# as said, content can be callable
def ul():
    return h.ul(h.li)
print h.div(ul)


<div><ul><li/></ul></div>

In [11]:
# adding more content inside the element
# operator used here += is same as tag.addContent method
h1 = h.h1('Header')
h1 += " 1."
h1 += h.span("2")
print h1


<h1>Header 1.<span>2</span></h1>

In [12]:
# concatenating elements
h1 = h.h1()
print concat(h1, h.h2(), h.h3)


<h1/><h2/><h3/>

In [13]:
# chain arguments
print h.h1(h.span(), h.span(), h.span())


<h1><span/><span/><span/></h1>

In [14]:
# chain arguments by list
print h.h1(*[h.span, h.span, h.span])


<h1><span/><span/><span/></h1>

In [15]:
# add attributes
print h.div(id="container", title="Content container")


<div id="container" title="Content container"/>

In [16]:
# add attributes by dictionary
print h.div(**{'id': "container", 'title':"Content container"})


<div id="container" title="Content container"/>

In [17]:
# using python reserved words can be tackled with uppercase letters or capitalization
# h.del or h.tag(class="") doesn't work but gives parse error. instead use something like:
print h.DEL(Class="reserved")


<del class="reserved"/>

In [18]:
# but if you really want uppercase tag names or attributes, 
# you can use setName and setAttribute methods
print h.create('DEL').setAttribute('Class', 'reserved')


<DEL Class="reserved"/>

In [19]:
# special attribute and tag names can be handled with setters.
# h.my-tag(dc:name = "special") doesn't work because of naming convention rules on python
# so you need to do:
print h.create("my-tag").addContent("content").setAttribute('dc:name', 'special')


<my-tag dc:name="special">content</my-tag>

Table extension class


In [20]:
class table(type(h.table())):
    def __init__(self, *args, **kw):
        pass
    
    def addCaption(self, caption, **kw):
        pass
    
    def addColGroup(self, *cols, **kw):
        pass
    
    def addHeadRow(self, *trs, **kw):
        pass
    
    def addFootRow(self, *trs, **kw):
        pass
    
    def addBodyRow(self, *trs, **kw):
        pass
    
    def addBodyRows(self, *trs, **kw):
        pass

In [21]:
# sure you can make tables with core table tags
tbl = h.table(CLASS="data")
tbl += h.thead(h.tr(*map(h.th, [1,2,3])))
tbl += h.tbody(*[h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])) for i in [1,2,3]])

print tbl
tbl


<table class="data"><thead><tr><th>1</th><th>2</th><th>3</th></tr></thead><tbody><tr><td>1.1</td><td>2.1</td><td>3.1</td></tr><tr><td>1.2</td><td>2.2</td><td>3.2</td></tr><tr><td>1.3</td><td>2.3</td><td>3.3</td></tr></tbody></table>
Out[21]:
123
1.12.13.1
1.22.23.2
1.32.33.3

In [22]:
# but using special table factory function structuring table is easier
from remarkuple import table

# initialize table
t = table(**{'id': 'data'})

# add caption title
t.addCaption('Caption')

columns = [{'style': 'background-color: red'},
           {'style': 'background-color: green'},
           {'style': 'background-color: blue'}]

# add column definitions
t.addColGroup(*[h.col(**attr) for attr in columns])

header = ['Column 1', 'Column 2', 'Column 3']

# add header row with column titles
t.addHeadRow(h.tr(*map(h.th, header)))

# add body rows
for i in range(1,3):
    t.addBodyRow(h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])))

# add separate bodies with rows
for i in range(3,5):
    t.addBodyRows(h.tr(*map(h.td, ["1.%s"%i,"2.%s"%i,"3.%s"%i])), id='tbody%s'%i)

# add footer row
t.addFootRow(h.tr(h.td('footer', colspan="3")))

t


Out[22]:
Caption
Column 1Column 2Column 3
footer
1.12.13.1
1.22.23.2
1.32.33.3
1.42.43.4

Some styles for table


In [23]:
%%html
<style type="text/css">
table#data { margin: 1em auto; border-collapse: collapse; border: 0} 
table#data caption { font-size: 1.2em; text-align: center; padding: 3px} 
table#data th, table#data td { padding: .25em; border: 1px solid #000; font-family: sans-serif; color: white} 
table#data th { color: #004900; font-weight: bold; text-align: left; } 
table#data thead th { border-bottom: 3px double #000; background-color: #ddd; text-align: center; } 
table#data tfoot td { border-top: 3px double #000; color: #fff; font-style: italic; font-size: .8em; text-align: center; background-color: brown} 
table#data tbody th { color: #000; }
table#data #tbody3 {font-weight: bold;font-size: 1.5em;}
table#data #tbody4 {font-style: italic;}
</style>


SVG class


In [24]:
class svg(type(h.svg())):
    
    def __init__(self, *args, **kw):
        pass
    
    def set_grid(self, boolean):
        pass
    
    def set_axis(self, boolean):
        pass
    
    def set_origin(self, boolean):
        pass
    
    def set_size(self, width, height):
        pass
    
    def set_text(self, *args, **kw):
        pass
    
    def set_rectangle(self, *args, **kw):
        pass
    
    def set_group(self, *args, **kw):
        pass
    
    def set_defs(self, *args, **kw):
        pass
    
    def set_line(self, *args, **kw):
        pass
    
    def set_circle(self, *args, **kw):
        pass
    
    def set_triangle(self, *args, **kw):
        pass
    
    def set_square(self, *args, **kw):
        pass
    
    def set_pentagon(self, *args, **kw):
        pass
    
    def set_hexagon(self, *args, **kw):
        pass
    
    def set_regular_polygon(self, *args, **kw):
        pass
    
    def polygon_points(self, vertex):
        pass
    
    def vertex(self, cx = 0, cy = 0, sides = 4, radius = 1, degrees = 0):
        pass

In [25]:
from remarkuple import svg

s = svg().set_axes().set_grid().set_origin()
s.set_circle(r=100, fill="darkgreen", stroke="white", style="fill-opacity: 75%")
s.set_circle(r=4, cx=0, cy=100)
s.set_text("(0,100)", x=5, y=105)

s


Out[25]:
(0,0)(0,100)

In [26]:
from numpy import linspace, pi, sin, cos
s = svg(width=200, height=200)
for x, y in enumerate(linspace(-pi, pi, 51)):
    s.set_circle(r=y+pi, cx=-100+x*2, cy=sin(y)*x)
    s.set_circle(r=pi-y, cx=x*2, cy=sin(y)*x)
    s.set_circle(r=pi-y, cx=x*2, cy=-sin(y)*x)
    s.set_circle(r=y+pi, cx=-100+x*2, cy=-sin(y)*x)

s


Out[26]:

The MIT License

Copyright (c) 2014 Marko Manninen