1""" 2Permission is hereby granted, free of charge, to any person obtaining a copy 3of this software and associated documentation files (the "Software"), to deal 4in the Software without restriction, including without limitation the rights 5to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6copies of the Software, and to permit persons to whom the Software is 7furnished to do so, subject to the following conditions: 8 9The above copyright notice and this permission notice shall be included in all 10copies or substantial portions of the Software. 11 12THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 17OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 18SOFTWARE. 19 20This file is originally from: https://bitbucket.org/hpk42/py, specifically: 21https://bitbucket.org/hpk42/py/src/980c8d526463958ee7cae678a7e4e9b054f36b94/py/_xmlgen.py?at=default 22by holger krekel, holger at merlinux eu. 2009 23""" 24import sys 25import re 26 27if sys.version_info >= (3, 0): 28 def u(s): 29 return s 30 31 def unicode(x): 32 if hasattr(x, '__unicode__'): 33 return x.__unicode__() 34 return str(x) 35else: 36 def u(s): 37 return unicode(s) 38 unicode = unicode 39 40 41class NamespaceMetaclass(type): 42 43 def __getattr__(self, name): 44 if name[:1] == '_': 45 raise AttributeError(name) 46 if self == Namespace: 47 raise ValueError("Namespace class is abstract") 48 tagspec = self.__tagspec__ 49 if tagspec is not None and name not in tagspec: 50 raise AttributeError(name) 51 classattr = {} 52 if self.__stickyname__: 53 classattr['xmlname'] = name 54 cls = type(name, (self.__tagclass__,), classattr) 55 setattr(self, name, cls) 56 return cls 57 58 59class Tag(list): 60 61 class Attr(object): 62 63 def __init__(self, **kwargs): 64 self.__dict__.update(kwargs) 65 66 def __init__(self, *args, **kwargs): 67 super(Tag, self).__init__(args) 68 self.attr = self.Attr(**kwargs) 69 70 def __unicode__(self): 71 return self.unicode(indent=0) 72 __str__ = __unicode__ 73 74 def unicode(self, indent=2): 75 l = [] 76 SimpleUnicodeVisitor(l.append, indent).visit(self) 77 return u("").join(l) 78 79 def __repr__(self): 80 name = self.__class__.__name__ 81 return "<%r tag object %d>" % (name, id(self)) 82 83Namespace = NamespaceMetaclass('Namespace', (object, ), { 84 '__tagspec__': None, 85 '__tagclass__': Tag, 86 '__stickyname__': False, 87}) 88 89 90class HtmlTag(Tag): 91 92 def unicode(self, indent=2): 93 l = [] 94 HtmlVisitor(l.append, indent, shortempty=False).visit(self) 95 return u("").join(l) 96 97# exported plain html namespace 98 99 100class html(Namespace): 101 __tagclass__ = HtmlTag 102 __stickyname__ = True 103 __tagspec__ = dict([(x, 1) for x in ( 104 'a,abbr,acronym,address,applet,area,b,bdo,big,blink,' 105 'blockquote,body,br,button,caption,center,cite,code,col,' 106 'colgroup,comment,dd,del,dfn,dir,div,dl,dt,em,embed,' 107 'fieldset,font,form,frameset,h1,h2,h3,h4,h5,h6,head,html,' 108 'i,iframe,img,input,ins,kbd,label,legend,li,link,listing,' 109 'map,marquee,menu,meta,multicol,nobr,noembed,noframes,' 110 'noscript,object,ol,optgroup,option,p,pre,q,s,script,' 111 'select,small,span,strike,strong,style,sub,sup,table,' 112 'tbody,td,textarea,tfoot,th,thead,title,tr,tt,u,ul,xmp,' 113 'base,basefont,frame,hr,isindex,param,samp,var' 114 ).split(',') if x]) 115 116 class Style(object): 117 118 def __init__(self, **kw): 119 for x, y in kw.items(): 120 x = x.replace('_', '-') 121 setattr(self, x, y) 122 123 124class raw(object): 125 """just a box that can contain a unicode string that will be 126 included directly in the output""" 127 128 def __init__(self, uniobj): 129 self.uniobj = uniobj 130 131 132class SimpleUnicodeVisitor(object): 133 """ recursive visitor to write unicode. """ 134 135 def __init__(self, write, indent=0, curindent=0, shortempty=True): 136 self.write = write 137 self.cache = {} 138 self.visited = {} # for detection of recursion 139 self.indent = indent 140 self.curindent = curindent 141 self.parents = [] 142 self.shortempty = shortempty # short empty tags or not 143 144 def visit(self, node): 145 """ dispatcher on node's class/bases name. """ 146 cls = node.__class__ 147 try: 148 visitmethod = self.cache[cls] 149 except KeyError: 150 for subclass in cls.__mro__: 151 visitmethod = getattr(self, subclass.__name__, None) 152 if visitmethod is not None: 153 break 154 else: 155 visitmethod = self.__object 156 self.cache[cls] = visitmethod 157 visitmethod(node) 158 159 # the default fallback handler is marked private 160 # to avoid clashes with the tag name object 161 def __object(self, obj): 162 # self.write(obj) 163 self.write(escape(unicode(obj))) 164 165 def raw(self, obj): 166 self.write(obj.uniobj) 167 168 def list(self, obj): 169 assert id(obj) not in self.visited 170 self.visited[id(obj)] = 1 171 for elem in obj: 172 self.visit(elem) 173 174 def Tag(self, tag): 175 assert id(tag) not in self.visited 176 try: 177 tag.parent = self.parents[-1] 178 except IndexError: 179 tag.parent = None 180 self.visited[id(tag)] = 1 181 tagname = getattr(tag, 'xmlname', tag.__class__.__name__) 182 if self.curindent and not self._isinline(tagname): 183 self.write("\n" + u(' ') * self.curindent) 184 if tag: 185 self.curindent += self.indent 186 self.write(u('<%s%s>') % (tagname, self.attributes(tag))) 187 self.parents.append(tag) 188 for x in tag: 189 self.visit(x) 190 self.parents.pop() 191 self.write(u('</%s>') % tagname) 192 self.curindent -= self.indent 193 else: 194 nameattr = tagname + self.attributes(tag) 195 if self._issingleton(tagname): 196 self.write(u('<%s/>') % (nameattr,)) 197 else: 198 self.write(u('<%s></%s>') % (nameattr, tagname)) 199 200 def attributes(self, tag): 201 # serialize attributes 202 attrlist = dir(tag.attr) 203 attrlist.sort() 204 l = [] 205 for name in attrlist: 206 res = self.repr_attribute(tag.attr, name) 207 if res is not None: 208 l.append(res) 209 l.extend(self.getstyle(tag)) 210 return u("").join(l) 211 212 def repr_attribute(self, attrs, name): 213 if name[:2] != '__': 214 value = getattr(attrs, name) 215 if name.endswith('_'): 216 name = name[:-1] 217 if isinstance(value, raw): 218 insert = value.uniobj 219 else: 220 insert = escape(unicode(value)) 221 return ' %s="%s"' % (name, insert) 222 223 def getstyle(self, tag): 224 """ return attribute list suitable for styling. """ 225 try: 226 styledict = tag.style.__dict__ 227 except AttributeError: 228 return [] 229 else: 230 stylelist = [x + ': ' + y for x, y in styledict.items()] 231 return [u(' style="%s"') % u('; ').join(stylelist)] 232 233 def _issingleton(self, tagname): 234 """can (and will) be overridden in subclasses""" 235 return self.shortempty 236 237 def _isinline(self, tagname): 238 """can (and will) be overridden in subclasses""" 239 return False 240 241 242class HtmlVisitor(SimpleUnicodeVisitor): 243 244 single = dict([(x, 1) for x in 245 ('br,img,area,param,col,hr,meta,link,base,' 246 'input,frame').split(',')]) 247 inline = dict([(x, 1) for x in 248 ('a abbr acronym b basefont bdo big br cite code dfn em font ' 249 'i img input kbd label q s samp select small span strike ' 250 'strong sub sup textarea tt u var'.split(' '))]) 251 252 def repr_attribute(self, attrs, name): 253 if name == 'class_': 254 value = getattr(attrs, name) 255 if value is None: 256 return 257 return super(HtmlVisitor, self).repr_attribute(attrs, name) 258 259 def _issingleton(self, tagname): 260 return tagname in self.single 261 262 def _isinline(self, tagname): 263 return tagname in self.inline 264 265 266class _escape: 267 268 def __init__(self): 269 self.escape = { 270 u('"'): u('"'), u('<'): u('<'), u('>'): u('>'), 271 u('&'): u('&'), u("'"): u('''), 272 } 273 self.charef_rex = re.compile(u("|").join(self.escape.keys())) 274 275 def _replacer(self, match): 276 return self.escape[match.group(0)] 277 278 def __call__(self, ustring): 279 """ xml-escape the given unicode string. """ 280 ustring = unicode(ustring) 281 return self.charef_rex.sub(self._replacer, ustring) 282 283escape = _escape() 284