Added a HTML-generation engine that might hopefully be useful.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 13 May 2012 01:51:22 +0000 (03:51 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 13 May 2012 01:51:22 +0000 (03:51 +0200)
setup.py
wrw/sp/__init__.py [new file with mode: 0644]
wrw/sp/cons.py [new file with mode: 0644]
wrw/sp/util.py [new file with mode: 0644]
wrw/sp/xhtml.py [new file with mode: 0644]

index a3f45d2..3775c63 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -7,6 +7,6 @@ setup(name = "wrw",
       description = "Simple WSGI request wrapper library",
       author = "Fredrik Tolf",
       author_email = "fredrik@dolda2000.com",
-      packages = ["wrw"],
+      packages = ["wrw", "wrw.sp"],
       package_data = {"wrw": ["makolib/*.mako"]},
       license = "GPL-3")
diff --git a/wrw/sp/__init__.py b/wrw/sp/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/wrw/sp/cons.py b/wrw/sp/cons.py
new file mode 100644 (file)
index 0000000..5341b20
--- /dev/null
@@ -0,0 +1,61 @@
+import xml.dom.minidom
+
+class node(object):
+    def __str__(self):
+        doc = xml.dom.minidom.Document()
+        return self.__todom__(doc).toxml()
+
+class text(node, unicode):
+    def __todom__(self, doc):
+        return doc.createTextNode(self)
+
+class element(node):
+    def __init__(self, ns, name, ctx):
+        self.ns = ns
+        self.name = unicode(name)
+        self.ctx = ctx
+        self.attrs = {}
+        self.children = []
+
+    def __call__(self, *children, **attrs):
+        for child in children:
+            self.children.append(self.ctx.nodefrom(child))
+        for k, v in attrs.iteritems():
+            self.attrs[unicode(k)] = unicode(v)
+        return self
+
+    def __todom__(self, doc):
+        el = doc.createElementNS(self.ns, self.name)
+        for k, v in self.attrs.iteritems():
+            el.setAttribute(k, v)
+        for child in self.children:
+            el.appendChild(child.__todom__(doc))
+        return el
+
+class context(object):
+    def __init__(self):
+        self.nodeconv = {}
+        self.nodeconv[str] = lambda ob: text(ob, "utf-8")
+        self.nodeconv[unicode] = text
+        self.nodeconv[int] = text
+        self.nodeconv[long] = text
+        self.nodeconv[float] = text
+
+    def nodefrom(self, ob):
+        if isinstance(ob, node):
+            return ob
+        if hasattr(ob, "__tonode__"):
+            return ob.__tonode__()
+        if type(ob) in self.nodeconv:
+            return self.nodeconv[type(ob)](ob)
+        raise Exception("No node conversion known for %s objects" % str(type(ob)))
+
+class constructor(object):
+    def __init__(self, ns, elcls = element, ctx=None):
+        self._ns = ns
+        self._elcls = elcls
+        if ctx is None: ctx = context()
+        self._ctx = ctx
+
+    def __getattr__(self, name):
+        return self._elcls(self._ns, name, self._ctx)
diff --git a/wrw/sp/util.py b/wrw/sp/util.py
new file mode 100644 (file)
index 0000000..96e9880
--- /dev/null
@@ -0,0 +1,201 @@
+import cons
+
+def findnsnames(el):
+    names = {}
+    nid = [1]
+    def proc(el):
+        if isinstance(el, cons.element):
+            if el.ns not in names:
+                names[el.ns] = u"n" + unicode(nid[0])
+                nid[:] = [nid[0] + 1]
+            for ch in el.children:
+                proc(ch)
+    proc(el)
+    if None in names:
+        names[None] = None
+    else:
+        names[el.ns] = None
+    return names
+
+class formatter(object):
+    def __init__(self, out, root, nsnames=None, charset="utf-8", doctype=None):
+        self.root = root
+        if nsnames is None:
+            nsnames = findnsnames(root)
+        self.nsnames = nsnames
+        self.out = out
+        self.charset = charset
+        self.doctype = doctype
+
+    def write(self, text):
+        self.out.write(text.encode(self.charset))
+
+    def quotewrite(self, buf):
+        for ch in buf:
+            if ch == u'&':
+                self.write(u"&amp;")
+            elif ch == u'<':
+                self.write(u"&lt;")
+            elif ch == u'>':
+                self.write(u"&gt;")
+            else:
+                self.write(ch)
+
+    def text(self, el):
+        self.quotewrite(el)
+
+    def attrval(self, buf):
+        qc, qt = (u"'", u"&apos;") if u'"' in buf else (u'"', u"&quot;")
+        self.write(qc)
+        for ch in buf:
+            if ch == u'&':
+                self.write(u"&amp;")
+            elif ch == u'<':
+                self.write(u"&lt;")
+            elif ch == u'>':
+                self.write(u"&gt;")
+            elif ch == qc:
+                self.write(qt)
+            else:
+                self.write(ch)
+        self.write(qc)
+
+    def attr(self, k, v):
+        self.write(k)
+        self.write(u'=')
+        self.attrval(v)
+
+    def shorttag(self, el, **extra):
+        self.write(u'<' + self.elname(el))
+        for k, v in el.attrs.iteritems():
+            self.write(u' ')
+            self.attr(k, v)
+        for k, v in extra.iteritems():
+            self.write(u' ')
+            self.attr(k, v)
+        self.write(u" />")
+
+    def elname(self, el):
+        ns = self.nsnames[el.ns]
+        if ns is None:
+            return el.name
+        else:
+            return ns + u':' + el.name
+
+    def starttag(self, el, **extra):
+        self.write(u'<' + self.elname(el))
+        for k, v in el.attrs.iteritems():
+            self.write(u' ')
+            self.attr(k, v)
+        for k, v in extra.iteritems():
+            self.write(u' ')
+            self.attr(k, v)
+        self.write(u'>')
+
+    def endtag(self, el):
+        self.write(u'</' + self.elname(el) + u'>')
+
+    def longtag(self, el):
+        self.starttag(el, **extra)
+        for ch in el.children:
+            self.node(ch)
+        self.endtag(el)
+
+    def element(self, el, **extra):
+        if len(el.children) == 0:
+            self.shorttag(el, **extra)
+        else:
+            self.longtag(el, **extra)
+
+    def node(self, el):
+        if isinstance(el, cons.element):
+            self.element(el)
+        elif isinstance(el, cons.text):
+            self.text(el)
+        else:
+            raise Exception("Unknown object in element tree: " + el)
+
+    def start(self):
+        self.write(u'<?xml version="1.0" encoding="' + self.charset + u'" ?>\n')
+        if self.doctype:
+            self.write(u'<!DOCTYPE %s PUBLIC "%s" "%s">\n' % (self.root.name,
+                                                              self.doctype[0],
+                                                              self.doctype[1]))
+        extra = {}
+        for uri, nm in self.nsnames.iteritems():
+            if uri is None:
+                continue
+            if nm is None:
+                extra[u"xmlns"] = uri
+            else:
+                extra[u"xmlns:" + nm] = uri
+        self.element(self.root, **extra)
+
+    @classmethod
+    def output(cls, out, el, *args, **kw):
+        cls(out=out, root=el, *args, **kw).start()
+
+    def update(self, **ch):
+        ret = type(self).__new__(type(self))
+        ret.__dict__.update(self.__dict__)
+        ret.__dict__.update(ch)
+        return ret
+
+class iwriter(object):
+    def __init__(self, out):
+        self.out = out
+        self.atbol = True
+        self.col = 0
+
+    def write(self, buf):
+        for c in buf:
+            if c == '\n':
+                self.col = 0
+            else:
+                self.col += 1
+            self.out.write(c)
+        self.atbol = False
+
+    def indent(self, indent):
+        if self.atbol:
+            return
+        if self.col != 0:
+            self.write('\n')
+        self.write(indent)
+        self.atbol = True
+
+class indenter(formatter):
+    def __init__(self, indent=u"  ", *args, **kw):
+        super(indenter, self).__init__(*args, **kw)
+        self.out = iwriter(self.out)
+        self.indent = indent
+        self.curind = u""
+
+    def simple(self, el):
+        for ch in el.children:
+            if not isinstance(ch, cons.text):
+                return False
+        return True
+
+    def longtag(self, el, **extra):
+        self.starttag(el, **extra)
+        sub = self
+        reind = False
+        if not self.simple(el):
+            sub = self.update(curind=self.curind + self.indent)
+            sub.out.indent(sub.curind)
+            reind = True
+        for ch in el.children:
+            sub.node(ch)
+        if reind:
+            self.out.indent(self.curind)
+        self.endtag(el)
+
+    def element(self, el, **extra):
+        super(indenter, self).element(el, **extra)
+        if self.out.col > 80 and self.simple(el):
+            self.out.indent(self.curind)
+
+    def start(self):
+        super(indenter, self).start()
+        self.write('\n')
diff --git a/wrw/sp/xhtml.py b/wrw/sp/xhtml.py
new file mode 100644 (file)
index 0000000..abf4b96
--- /dev/null
@@ -0,0 +1,53 @@
+import xml.dom.minidom, StringIO
+import cons as _cons
+import util
+dom = xml.dom.minidom.getDOMImplementation()
+
+ns = u"http://www.w3.org/1999/xhtml"
+doctype = u"-//W3C//DTD XHTML 1.1//EN"
+dtd = u"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
+
+class htmlelement(_cons.element):
+    def __todoc__(self):
+        doc = dom.createDocument(None, None, None)
+        doc.appendChild(dom.createDocumentType("html", doctype, dtd))
+        doc.appendChild(self.__todom__(doc))
+        return doc
+
+def cons(ctx=None):
+    return _cons.constructor(ns, htmlelement, ctx)
+
+def head(title=None, css=None):
+    h = cons()
+    head = h.head
+    if title:
+        head(h.title(title))
+    if isinstance(css, str) or isinstance(css, unicode):
+        head(h.link(rel="stylesheet", type="text/css", href=css))
+    elif css:
+        for ss in css:
+            head(h.link(rel="stylesheet", type="text/css", href=ss))
+    return head
+
+class htmlformatter(util.formatter):
+    allowshort = set([u"br", u"hr", u"img", u"input"])
+    def element(self, el, **extra):
+        if el.name in self.allowshort:
+            super(htmlformatter, self).element(el, **extra)
+        else:
+            self.longtag(el, **extra)
+
+class htmlindenter(util.indenter, htmlformatter):
+    pass
+
+def forreq(req, tree):
+    # XXX: Use proper Content-Type for clients accepting it.
+    req.ohead["Content-Type"] = "text/html; charset=utf-8"
+    buf = StringIO.StringIO()
+    htmlindenter.output(buf, tree, doctype=(doctype, dtd), charset="utf-8")
+    return [buf.getvalue()]
+
+def xhtmlresp(callable):
+    def wrapper(req):
+        return forreq(req, callable(req))
+    return wrapper