reviewing, refactoring, porting xml/html object/tree generation to work with 3k
--HG-- branch : trunk
This commit is contained in:
parent
6823fa634b
commit
a0d7ab2244
|
@ -186,11 +186,11 @@ initpkg(__name__,
|
||||||
|
|
||||||
# small and mean xml/html generation
|
# small and mean xml/html generation
|
||||||
'xml.__doc__' : ('./xmlobj/__init__.py', '__doc__'),
|
'xml.__doc__' : ('./xmlobj/__init__.py', '__doc__'),
|
||||||
'xml.html' : ('./xmlobj/html.py', 'html'),
|
'xml.html' : ('./xmlobj/xmlgen.py', 'html'),
|
||||||
'xml.Tag' : ('./xmlobj/xml.py', 'Tag'),
|
'xml.Tag' : ('./xmlobj/xmlgen.py', 'Tag'),
|
||||||
'xml.raw' : ('./xmlobj/xml.py', 'raw'),
|
'xml.raw' : ('./xmlobj/xmlgen.py', 'raw'),
|
||||||
'xml.Namespace' : ('./xmlobj/xml.py', 'Namespace'),
|
'xml.Namespace' : ('./xmlobj/xmlgen.py', 'Namespace'),
|
||||||
'xml.escape' : ('./xmlobj/misc.py', 'escape'),
|
'xml.escape' : ('./xmlobj/xmlgen.py', 'escape'),
|
||||||
|
|
||||||
# logging API ('producers' and 'consumers' connected via keywords)
|
# logging API ('producers' and 'consumers' connected via keywords)
|
||||||
'log.__doc__' : ('./log/__init__.py', '__doc__'),
|
'log.__doc__' : ('./log/__init__.py', '__doc__'),
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
from py.xml import Namespace, Tag
|
|
||||||
from py.__.xmlobj.visit import SimpleUnicodeVisitor
|
|
||||||
|
|
||||||
class HtmlVisitor(SimpleUnicodeVisitor):
|
|
||||||
|
|
||||||
single = dict([(x, 1) for x in
|
|
||||||
('br,img,area,param,col,hr,meta,link,base,'
|
|
||||||
'input,frame').split(',')])
|
|
||||||
inline = dict([(x, 1) for x in
|
|
||||||
('a abbr acronym b basefont bdo big br cite code dfn em font '
|
|
||||||
'i img input kbd label q s samp select small span strike '
|
|
||||||
'strong sub sup textarea tt u var'.split(' '))])
|
|
||||||
|
|
||||||
def repr_attribute(self, attrs, name):
|
|
||||||
if name == 'class_':
|
|
||||||
value = getattr(attrs, name)
|
|
||||||
if value is None:
|
|
||||||
return
|
|
||||||
return super(HtmlVisitor, self).repr_attribute(attrs, name)
|
|
||||||
|
|
||||||
def _issingleton(self, tagname):
|
|
||||||
return tagname in self.single
|
|
||||||
|
|
||||||
def _isinline(self, tagname):
|
|
||||||
return tagname in self.inline
|
|
||||||
|
|
||||||
if sys.version_info > (3, 0):
|
|
||||||
def u(s): return s
|
|
||||||
else:
|
|
||||||
def u(s): return unicode(s)
|
|
||||||
|
|
||||||
class HtmlTag(Tag):
|
|
||||||
def unicode(self, indent=2):
|
|
||||||
l = []
|
|
||||||
HtmlVisitor(l.append, indent, shortempty=False).visit(self)
|
|
||||||
return u("").join(l)
|
|
||||||
|
|
||||||
# exported plain html namespace
|
|
||||||
class html(Namespace):
|
|
||||||
__tagclass__ = HtmlTag
|
|
||||||
__stickyname__ = True
|
|
||||||
__tagspec__ = dict([(x,1) for x in (
|
|
||||||
'a,abbr,acronym,address,applet,area,b,bdo,big,blink,'
|
|
||||||
'blockquote,body,br,button,caption,center,cite,code,col,'
|
|
||||||
'colgroup,comment,dd,del,dfn,dir,div,dl,dt,em,embed,'
|
|
||||||
'fieldset,font,form,frameset,h1,h2,h3,h4,h5,h6,head,html,'
|
|
||||||
'i,iframe,img,input,ins,kbd,label,legend,li,link,listing,'
|
|
||||||
'map,marquee,menu,meta,multicol,nobr,noembed,noframes,'
|
|
||||||
'noscript,object,ol,optgroup,option,p,pre,q,s,script,'
|
|
||||||
'select,small,span,strike,strong,style,sub,sup,table,'
|
|
||||||
'tbody,td,textarea,tfoot,th,thead,title,tr,tt,u,ul,xmp,'
|
|
||||||
'base,basefont,frame,hr,isindex,param,samp,var'
|
|
||||||
).split(',') if x])
|
|
||||||
|
|
||||||
class Style(object):
|
|
||||||
def __init__(self, **kw):
|
|
||||||
for x, y in kw.items():
|
|
||||||
x = x.replace('_', '-')
|
|
||||||
setattr(self, x, y)
|
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.version_info > (3, 0):
|
|
||||||
def u(s):
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
def u(s):
|
|
||||||
return unicode(s)
|
|
||||||
|
|
||||||
class _escape:
|
|
||||||
def __init__(self):
|
|
||||||
self.escape = {
|
|
||||||
u('"') : u('"'), u('<') : u('<'), u('>') : u('>'),
|
|
||||||
u('&') : u('&'), u("'") : u('''),
|
|
||||||
}
|
|
||||||
self.charef_rex = re.compile(u("|").join(self.escape.keys()))
|
|
||||||
|
|
||||||
def _replacer(self, match):
|
|
||||||
return self.escape[match.group(0)]
|
|
||||||
|
|
||||||
def __call__(self, ustring):
|
|
||||||
""" xml-escape the given unicode string. """
|
|
||||||
return self.charef_rex.sub(self._replacer, ustring)
|
|
||||||
|
|
||||||
escape = _escape()
|
|
|
@ -1,54 +0,0 @@
|
||||||
from py.xml import html
|
|
||||||
|
|
||||||
def test_html_name_stickyness():
|
|
||||||
class my(html.p):
|
|
||||||
pass
|
|
||||||
x = my("hello")
|
|
||||||
assert unicode(x) == '<p>hello</p>'
|
|
||||||
|
|
||||||
def test_stylenames():
|
|
||||||
class my:
|
|
||||||
class body(html.body):
|
|
||||||
style = html.Style(font_size = "12pt")
|
|
||||||
u = unicode(my.body())
|
|
||||||
assert u == '<body style="font-size: 12pt"></body>'
|
|
||||||
|
|
||||||
def test_class_None():
|
|
||||||
t = html.body(class_=None)
|
|
||||||
u = unicode(t)
|
|
||||||
assert u == '<body></body>'
|
|
||||||
|
|
||||||
def test_alternating_style():
|
|
||||||
alternating = (
|
|
||||||
html.Style(background="white"),
|
|
||||||
html.Style(background="grey"),
|
|
||||||
)
|
|
||||||
class my(html):
|
|
||||||
class li(html.li):
|
|
||||||
def style(self):
|
|
||||||
i = self.parent.index(self)
|
|
||||||
return alternating[i%2]
|
|
||||||
style = property(style)
|
|
||||||
|
|
||||||
x = my.ul(
|
|
||||||
my.li("hello"),
|
|
||||||
my.li("world"),
|
|
||||||
my.li("42"))
|
|
||||||
u = unicode(x)
|
|
||||||
assert u == ('<ul><li style="background: white">hello</li>'
|
|
||||||
'<li style="background: grey">world</li>'
|
|
||||||
'<li style="background: white">42</li>'
|
|
||||||
'</ul>')
|
|
||||||
|
|
||||||
def test_singleton():
|
|
||||||
h = html.head(html.link(href="foo"))
|
|
||||||
assert unicode(h) == '<head><link href="foo"/></head>'
|
|
||||||
|
|
||||||
h = html.head(html.script(src="foo"))
|
|
||||||
assert unicode(h) == '<head><script src="foo"></script></head>'
|
|
||||||
|
|
||||||
def test_inline():
|
|
||||||
h = html.div(html.span('foo'), html.span('bar'))
|
|
||||||
assert (h.unicode(indent=2) ==
|
|
||||||
'<div><span>foo</span><span>bar</span></div>')
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
from py.__.xmlobj.xmlgen import unicode, html
|
||||||
|
|
||||||
class ns(py.xml.Namespace):
|
class ns(py.xml.Namespace):
|
||||||
pass
|
pass
|
||||||
|
@ -56,3 +57,56 @@ def test_raw():
|
||||||
u = unicode(x)
|
u = unicode(x)
|
||||||
assert u == "<some><p>literal</p></some>"
|
assert u == "<some><p>literal</p></some>"
|
||||||
|
|
||||||
|
|
||||||
|
def test_html_name_stickyness():
|
||||||
|
class my(html.p):
|
||||||
|
pass
|
||||||
|
x = my("hello")
|
||||||
|
assert unicode(x) == '<p>hello</p>'
|
||||||
|
|
||||||
|
def test_stylenames():
|
||||||
|
class my:
|
||||||
|
class body(html.body):
|
||||||
|
style = html.Style(font_size = "12pt")
|
||||||
|
u = unicode(my.body())
|
||||||
|
assert u == '<body style="font-size: 12pt"></body>'
|
||||||
|
|
||||||
|
def test_class_None():
|
||||||
|
t = html.body(class_=None)
|
||||||
|
u = unicode(t)
|
||||||
|
assert u == '<body></body>'
|
||||||
|
|
||||||
|
def test_alternating_style():
|
||||||
|
alternating = (
|
||||||
|
html.Style(background="white"),
|
||||||
|
html.Style(background="grey"),
|
||||||
|
)
|
||||||
|
class my(html):
|
||||||
|
class li(html.li):
|
||||||
|
def style(self):
|
||||||
|
i = self.parent.index(self)
|
||||||
|
return alternating[i%2]
|
||||||
|
style = property(style)
|
||||||
|
|
||||||
|
x = my.ul(
|
||||||
|
my.li("hello"),
|
||||||
|
my.li("world"),
|
||||||
|
my.li("42"))
|
||||||
|
u = unicode(x)
|
||||||
|
assert u == ('<ul><li style="background: white">hello</li>'
|
||||||
|
'<li style="background: grey">world</li>'
|
||||||
|
'<li style="background: white">42</li>'
|
||||||
|
'</ul>')
|
||||||
|
|
||||||
|
def test_singleton():
|
||||||
|
h = html.head(html.link(href="foo"))
|
||||||
|
assert unicode(h) == '<head><link href="foo"/></head>'
|
||||||
|
|
||||||
|
h = html.head(html.script(src="foo"))
|
||||||
|
assert unicode(h) == '<head><script src="foo"></script></head>'
|
||||||
|
|
||||||
|
def test_inline():
|
||||||
|
h = html.div(html.span('foo'), html.span('bar'))
|
||||||
|
assert (h.unicode(indent=2) ==
|
||||||
|
'<div><span>foo</span><span>bar</span></div>')
|
||||||
|
|
||||||
|
|
|
@ -1,114 +0,0 @@
|
||||||
#
|
|
||||||
# a generic conversion serializer
|
|
||||||
#
|
|
||||||
|
|
||||||
import sys
|
|
||||||
from py.xml import escape
|
|
||||||
|
|
||||||
if sys.version_info > (3, 0):
|
|
||||||
def u(s):
|
|
||||||
return s
|
|
||||||
else:
|
|
||||||
def u(s):
|
|
||||||
return unicode(s)
|
|
||||||
|
|
||||||
class SimpleUnicodeVisitor(object):
|
|
||||||
""" recursive visitor to write unicode. """
|
|
||||||
def __init__(self, write, indent=0, curindent=0, shortempty=True):
|
|
||||||
self.write = write
|
|
||||||
self.cache = {}
|
|
||||||
self.visited = {} # for detection of recursion
|
|
||||||
self.indent = indent
|
|
||||||
self.curindent = curindent
|
|
||||||
self.parents = []
|
|
||||||
self.shortempty = shortempty # short empty tags or not
|
|
||||||
|
|
||||||
def visit(self, node):
|
|
||||||
""" dispatcher on node's class/bases name. """
|
|
||||||
cls = node.__class__
|
|
||||||
try:
|
|
||||||
visitmethod = self.cache[cls]
|
|
||||||
except KeyError:
|
|
||||||
for subclass in cls.__mro__:
|
|
||||||
visitmethod = getattr(self, subclass.__name__, None)
|
|
||||||
if visitmethod is not None:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
visitmethod = self.object
|
|
||||||
self.cache[cls] = visitmethod
|
|
||||||
visitmethod(node)
|
|
||||||
|
|
||||||
def object(self, obj):
|
|
||||||
#self.write(obj)
|
|
||||||
self.write(escape(unicode(obj)))
|
|
||||||
|
|
||||||
def raw(self, obj):
|
|
||||||
self.write(obj.uniobj)
|
|
||||||
|
|
||||||
def list(self, obj):
|
|
||||||
assert id(obj) not in self.visited
|
|
||||||
self.visited[id(obj)] = 1
|
|
||||||
map(self.visit, obj)
|
|
||||||
|
|
||||||
def Tag(self, tag):
|
|
||||||
assert id(tag) not in self.visited
|
|
||||||
try:
|
|
||||||
tag.parent = self.parents[-1]
|
|
||||||
except IndexError:
|
|
||||||
tag.parent = None
|
|
||||||
self.visited[id(tag)] = 1
|
|
||||||
tagname = getattr(tag, 'xmlname', tag.__class__.__name__)
|
|
||||||
if self.curindent and not self._isinline(tagname):
|
|
||||||
self.write("\n" + u(' ') * self.curindent)
|
|
||||||
if tag:
|
|
||||||
self.curindent += self.indent
|
|
||||||
self.write(u('<%s%s>') % (tagname, self.attributes(tag)))
|
|
||||||
self.parents.append(tag)
|
|
||||||
map(self.visit, tag)
|
|
||||||
self.parents.pop()
|
|
||||||
self.write(u('</%s>') % tagname)
|
|
||||||
self.curindent -= self.indent
|
|
||||||
else:
|
|
||||||
nameattr = tagname+self.attributes(tag)
|
|
||||||
if self._issingleton(tagname):
|
|
||||||
self.write(u('<%s/>') % (nameattr,))
|
|
||||||
else:
|
|
||||||
self.write(u('<%s></%s>') % (nameattr, tagname))
|
|
||||||
|
|
||||||
def attributes(self, tag):
|
|
||||||
# serialize attributes
|
|
||||||
attrlist = dir(tag.attr)
|
|
||||||
attrlist.sort()
|
|
||||||
l = []
|
|
||||||
for name in attrlist:
|
|
||||||
res = self.repr_attribute(tag.attr, name)
|
|
||||||
if res is not None:
|
|
||||||
l.append(res)
|
|
||||||
l.extend(self.getstyle(tag))
|
|
||||||
return u("").join(l)
|
|
||||||
|
|
||||||
def repr_attribute(self, attrs, name):
|
|
||||||
if name[:2] != '__':
|
|
||||||
value = getattr(attrs, name)
|
|
||||||
if name.endswith('_'):
|
|
||||||
name = name[:-1]
|
|
||||||
return u(' %s="%s"') % (name, escape(unicode(value)))
|
|
||||||
|
|
||||||
def getstyle(self, tag):
|
|
||||||
""" return attribute list suitable for styling. """
|
|
||||||
try:
|
|
||||||
styledict = tag.style.__dict__
|
|
||||||
except AttributeError:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
stylelist = [x+': ' + y for x,y in styledict.items()]
|
|
||||||
return [u(' style="%s"') % u('; ').join(stylelist)]
|
|
||||||
|
|
||||||
def _issingleton(self, tagname):
|
|
||||||
"""can (and will) be overridden in subclasses"""
|
|
||||||
return self.shortempty
|
|
||||||
|
|
||||||
def _isinline(self, tagname):
|
|
||||||
"""can (and will) be overridden in subclasses"""
|
|
||||||
return False
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
"""
|
|
||||||
generic (and pythonic :-) xml tag and namespace objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Tag(list):
|
|
||||||
class Attr(object):
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.__dict__.update(kwargs)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(Tag, self).__init__(args)
|
|
||||||
self.attr = self.Attr(**kwargs)
|
|
||||||
|
|
||||||
def __unicode__(self):
|
|
||||||
return self.unicode(indent=0)
|
|
||||||
|
|
||||||
def unicode(self, indent=2):
|
|
||||||
from py.__.xmlobj.visit import SimpleUnicodeVisitor
|
|
||||||
l = []
|
|
||||||
SimpleUnicodeVisitor(l.append, indent).visit(self)
|
|
||||||
return "".join(l)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
name = self.__class__.__name__
|
|
||||||
return "<%r tag object %d>" % (name, id(self))
|
|
||||||
|
|
||||||
class raw(object):
|
|
||||||
"""just a box that can contain a unicode string that will be
|
|
||||||
included directly in the output"""
|
|
||||||
def __init__(self, uniobj):
|
|
||||||
self.uniobj = uniobj
|
|
||||||
|
|
||||||
# the generic xml namespace
|
|
||||||
# provides Tag classes on the fly optionally checking for
|
|
||||||
# a tagspecification
|
|
||||||
|
|
||||||
class NamespaceMetaclass(type):
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if name[:1] == '_':
|
|
||||||
raise AttributeError(name)
|
|
||||||
if self == Namespace:
|
|
||||||
raise ValueError("Namespace class is abstract")
|
|
||||||
tagspec = self.__tagspec__
|
|
||||||
if tagspec is not None and name not in tagspec:
|
|
||||||
raise AttributeError(name)
|
|
||||||
classattr = {}
|
|
||||||
if self.__stickyname__:
|
|
||||||
classattr['xmlname'] = name
|
|
||||||
cls = type(name, (self.__tagclass__,), classattr)
|
|
||||||
setattr(self, name, cls)
|
|
||||||
return cls
|
|
||||||
|
|
||||||
class Namespace(object):
|
|
||||||
__tagspec__ = None
|
|
||||||
__tagclass__ = Tag
|
|
||||||
__metaclass__ = NamespaceMetaclass
|
|
||||||
__stickyname__ = False
|
|
||||||
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
"""
|
||||||
|
module for generating and serializing xml and html structures
|
||||||
|
by using simple python objects.
|
||||||
|
|
||||||
|
(c) holger krekel, holger at merlinux eu. 2009
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
import sys, re
|
||||||
|
|
||||||
|
if sys.version_info >= (3,0):
|
||||||
|
def u(s):
|
||||||
|
return s
|
||||||
|
def unicode(x):
|
||||||
|
if hasattr(x, '__unicode__'):
|
||||||
|
return x.__unicode__()
|
||||||
|
return str(x)
|
||||||
|
else:
|
||||||
|
def u(s):
|
||||||
|
return unicode(s)
|
||||||
|
unicode = unicode
|
||||||
|
|
||||||
|
|
||||||
|
class NamespaceMetaclass(type):
|
||||||
|
def __getattr__(self, name):
|
||||||
|
if name[:1] == '_':
|
||||||
|
raise AttributeError(name)
|
||||||
|
if self == Namespace:
|
||||||
|
raise ValueError("Namespace class is abstract")
|
||||||
|
tagspec = self.__tagspec__
|
||||||
|
if tagspec is not None and name not in tagspec:
|
||||||
|
raise AttributeError(name)
|
||||||
|
classattr = {}
|
||||||
|
if self.__stickyname__:
|
||||||
|
classattr['xmlname'] = name
|
||||||
|
cls = type(name, (self.__tagclass__,), classattr)
|
||||||
|
setattr(self, name, cls)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
class Tag(list):
|
||||||
|
class Attr(object):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.__dict__.update(kwargs)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Tag, self).__init__(args)
|
||||||
|
self.attr = self.Attr(**kwargs)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.unicode(indent=0)
|
||||||
|
__str__ = __unicode__
|
||||||
|
|
||||||
|
def unicode(self, indent=2):
|
||||||
|
l = []
|
||||||
|
SimpleUnicodeVisitor(l.append, indent).visit(self)
|
||||||
|
return "".join(l)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
name = self.__class__.__name__
|
||||||
|
return "<%r tag object %d>" % (name, id(self))
|
||||||
|
|
||||||
|
Namespace = NamespaceMetaclass('Namespace', (object, ), {
|
||||||
|
'__tagspec__': None,
|
||||||
|
'__tagclass__': Tag,
|
||||||
|
'__stickyname__': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
class HtmlTag(Tag):
|
||||||
|
def unicode(self, indent=2):
|
||||||
|
l = []
|
||||||
|
HtmlVisitor(l.append, indent, shortempty=False).visit(self)
|
||||||
|
return u("").join(l)
|
||||||
|
|
||||||
|
# exported plain html namespace
|
||||||
|
class html(Namespace):
|
||||||
|
__tagclass__ = HtmlTag
|
||||||
|
__stickyname__ = True
|
||||||
|
__tagspec__ = dict([(x,1) for x in (
|
||||||
|
'a,abbr,acronym,address,applet,area,b,bdo,big,blink,'
|
||||||
|
'blockquote,body,br,button,caption,center,cite,code,col,'
|
||||||
|
'colgroup,comment,dd,del,dfn,dir,div,dl,dt,em,embed,'
|
||||||
|
'fieldset,font,form,frameset,h1,h2,h3,h4,h5,h6,head,html,'
|
||||||
|
'i,iframe,img,input,ins,kbd,label,legend,li,link,listing,'
|
||||||
|
'map,marquee,menu,meta,multicol,nobr,noembed,noframes,'
|
||||||
|
'noscript,object,ol,optgroup,option,p,pre,q,s,script,'
|
||||||
|
'select,small,span,strike,strong,style,sub,sup,table,'
|
||||||
|
'tbody,td,textarea,tfoot,th,thead,title,tr,tt,u,ul,xmp,'
|
||||||
|
'base,basefont,frame,hr,isindex,param,samp,var'
|
||||||
|
).split(',') if x])
|
||||||
|
|
||||||
|
class Style(object):
|
||||||
|
def __init__(self, **kw):
|
||||||
|
for x, y in kw.items():
|
||||||
|
x = x.replace('_', '-')
|
||||||
|
setattr(self, x, y)
|
||||||
|
|
||||||
|
|
||||||
|
class raw(object):
|
||||||
|
"""just a box that can contain a unicode string that will be
|
||||||
|
included directly in the output"""
|
||||||
|
def __init__(self, uniobj):
|
||||||
|
self.uniobj = uniobj
|
||||||
|
|
||||||
|
class SimpleUnicodeVisitor(object):
|
||||||
|
""" recursive visitor to write unicode. """
|
||||||
|
def __init__(self, write, indent=0, curindent=0, shortempty=True):
|
||||||
|
self.write = write
|
||||||
|
self.cache = {}
|
||||||
|
self.visited = {} # for detection of recursion
|
||||||
|
self.indent = indent
|
||||||
|
self.curindent = curindent
|
||||||
|
self.parents = []
|
||||||
|
self.shortempty = shortempty # short empty tags or not
|
||||||
|
|
||||||
|
def visit(self, node):
|
||||||
|
""" dispatcher on node's class/bases name. """
|
||||||
|
cls = node.__class__
|
||||||
|
try:
|
||||||
|
visitmethod = self.cache[cls]
|
||||||
|
except KeyError:
|
||||||
|
for subclass in cls.__mro__:
|
||||||
|
visitmethod = getattr(self, subclass.__name__, None)
|
||||||
|
if visitmethod is not None:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
visitmethod = self.object
|
||||||
|
self.cache[cls] = visitmethod
|
||||||
|
visitmethod(node)
|
||||||
|
|
||||||
|
def object(self, obj):
|
||||||
|
#self.write(obj)
|
||||||
|
self.write(escape(unicode(obj)))
|
||||||
|
|
||||||
|
def raw(self, obj):
|
||||||
|
self.write(obj.uniobj)
|
||||||
|
|
||||||
|
def list(self, obj):
|
||||||
|
assert id(obj) not in self.visited
|
||||||
|
self.visited[id(obj)] = 1
|
||||||
|
map(self.visit, obj)
|
||||||
|
|
||||||
|
def Tag(self, tag):
|
||||||
|
assert id(tag) not in self.visited
|
||||||
|
try:
|
||||||
|
tag.parent = self.parents[-1]
|
||||||
|
except IndexError:
|
||||||
|
tag.parent = None
|
||||||
|
self.visited[id(tag)] = 1
|
||||||
|
tagname = getattr(tag, 'xmlname', tag.__class__.__name__)
|
||||||
|
if self.curindent and not self._isinline(tagname):
|
||||||
|
self.write("\n" + u(' ') * self.curindent)
|
||||||
|
if tag:
|
||||||
|
self.curindent += self.indent
|
||||||
|
self.write(u('<%s%s>') % (tagname, self.attributes(tag)))
|
||||||
|
self.parents.append(tag)
|
||||||
|
for x in tag:
|
||||||
|
self.visit(x)
|
||||||
|
self.parents.pop()
|
||||||
|
self.write(u('</%s>') % tagname)
|
||||||
|
self.curindent -= self.indent
|
||||||
|
else:
|
||||||
|
nameattr = tagname+self.attributes(tag)
|
||||||
|
if self._issingleton(tagname):
|
||||||
|
self.write(u('<%s/>') % (nameattr,))
|
||||||
|
else:
|
||||||
|
self.write(u('<%s></%s>') % (nameattr, tagname))
|
||||||
|
|
||||||
|
def attributes(self, tag):
|
||||||
|
# serialize attributes
|
||||||
|
attrlist = dir(tag.attr)
|
||||||
|
attrlist.sort()
|
||||||
|
l = []
|
||||||
|
for name in attrlist:
|
||||||
|
res = self.repr_attribute(tag.attr, name)
|
||||||
|
if res is not None:
|
||||||
|
l.append(res)
|
||||||
|
l.extend(self.getstyle(tag))
|
||||||
|
return u("").join(l)
|
||||||
|
|
||||||
|
def repr_attribute(self, attrs, name):
|
||||||
|
if name[:2] != '__':
|
||||||
|
value = getattr(attrs, name)
|
||||||
|
if name.endswith('_'):
|
||||||
|
name = name[:-1]
|
||||||
|
return ' %s="%s"' % (name, escape(unicode(value)))
|
||||||
|
|
||||||
|
def getstyle(self, tag):
|
||||||
|
""" return attribute list suitable for styling. """
|
||||||
|
try:
|
||||||
|
styledict = tag.style.__dict__
|
||||||
|
except AttributeError:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
stylelist = [x+': ' + y for x,y in styledict.items()]
|
||||||
|
return [u(' style="%s"') % u('; ').join(stylelist)]
|
||||||
|
|
||||||
|
def _issingleton(self, tagname):
|
||||||
|
"""can (and will) be overridden in subclasses"""
|
||||||
|
return self.shortempty
|
||||||
|
|
||||||
|
def _isinline(self, tagname):
|
||||||
|
"""can (and will) be overridden in subclasses"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
class HtmlVisitor(SimpleUnicodeVisitor):
|
||||||
|
|
||||||
|
single = dict([(x, 1) for x in
|
||||||
|
('br,img,area,param,col,hr,meta,link,base,'
|
||||||
|
'input,frame').split(',')])
|
||||||
|
inline = dict([(x, 1) for x in
|
||||||
|
('a abbr acronym b basefont bdo big br cite code dfn em font '
|
||||||
|
'i img input kbd label q s samp select small span strike '
|
||||||
|
'strong sub sup textarea tt u var'.split(' '))])
|
||||||
|
|
||||||
|
def repr_attribute(self, attrs, name):
|
||||||
|
if name == 'class_':
|
||||||
|
value = getattr(attrs, name)
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
return super(HtmlVisitor, self).repr_attribute(attrs, name)
|
||||||
|
|
||||||
|
def _issingleton(self, tagname):
|
||||||
|
return tagname in self.single
|
||||||
|
|
||||||
|
def _isinline(self, tagname):
|
||||||
|
return tagname in self.inline
|
||||||
|
|
||||||
|
|
||||||
|
class _escape:
|
||||||
|
def __init__(self):
|
||||||
|
self.escape = {
|
||||||
|
u('"') : u('"'), u('<') : u('<'), u('>') : u('>'),
|
||||||
|
u('&') : u('&'), u("'") : u('''),
|
||||||
|
}
|
||||||
|
self.charef_rex = re.compile(u("|").join(self.escape.keys()))
|
||||||
|
|
||||||
|
def _replacer(self, match):
|
||||||
|
return self.escape[match.group(0)]
|
||||||
|
|
||||||
|
def __call__(self, ustring):
|
||||||
|
""" xml-escape the given unicode string. """
|
||||||
|
return self.charef_rex.sub(self._replacer, ustring)
|
||||||
|
|
||||||
|
escape = _escape()
|
Loading…
Reference in New Issue