#!/usr/bin/env python #------------------------------------------------------------------------------- from __future__ import with_statement #------------------------------------------------------------------------------- from xml.etree.ElementTree import TreeBuilder,tostring #------------------------------------------------------------------------------- __all__ = ["XMLBuilder"] __doc__ = """ XMLBuilder is simple library build on top of ElementTree.TreeBuilder to simplify xml files creation as much as possible. Althow it can produce structured result with identated child tags. `XMLBuilder` use python `with` statement to define xml tag levels and `<<` operator for simple cases - text and tag without childs. from __future__ import with_statement from xmlbuilder import XMLBuilder x = XMLBuilder(format=True) with x.root(a = 1): with x.data: [x << ('node',{'val':i}) for i in range(10)] etree_node = ~x print str(x) """ #------------------------------------------------------------------------------- class _XMLNode(object): """Class for internal usage""" def __init__(self,parent,name,builder): self.builder = builder self.name = name self.text = [] self.attrs = {} self.entered = False self.parent = parent def __call__(self,*dt,**mp): text = "".join(dt) if self.entered: self.builder.data(text) else: self.text.append(text) if self.entered: raise ValueError("Can't add attributes to already opened element") smp = dict((k,str(v)) for k,v in mp.items()) self.attrs.update(smp) return self def __enter__(self): self.parent += 1 self.builder.start(self.name,self.attrs) self.builder.data("".join(self.text)) self.entered = True return self def __exit__(self,x,y,z): self.parent -= 1 self.builder.end(self.name) return False #------------------------------------------------------------------------------- class XMLBuilder(object): """XmlBuilder(encoding = 'utf-8', # result xml file encoding builder = None, #etree.TreeBuilder or compatible class tab_level = None, #current tabulation level - string format = False, # make formatted output tab_step = " " * 4) # tabulation step use str(builder) or unicode(builder) to get xml text or ~builder to obtaine etree.ElementTree """ def __init__(self,encoding = 'utf-8', builder = None, tab_level = None, format = False, tab_step = " " * 4): self.__builder = builder or TreeBuilder() self.__encoding = encoding if format : if tab_level is None: tab_level = "" if tab_level is not None: if not format: raise ValueError("format is False, but tab_level not None") self.__tab_level = tab_level # current format level self.__tab_step = tab_step # format step self.__has_sub_tag = False # True, if current tag had childrens self.__node = None # called from _XMLNode when tag opened def __iadd__(self,val): self.__has_sub_tag = False if self.__tab_level is not None: self.__builder.data("\n" + self.__tab_level) self.__tab_level += self.__tab_step return self # called from XMLNode when tag closed def __isub__(self,val): if self.__tab_level is not None: self.__tab_level = self.__tab_level[:-len(self.__tab_step)] if self.__has_sub_tag: self.__builder.data("\n" + self.__tab_level) self.__has_sub_tag = True return self def __getattr__(self,name): return _XMLNode(self,name,self.__builder) def __call__(self,name,*dt,**mp): x = _XMLNode(self,name,self.__builder) x(*dt,**mp) return x #create new tag or add text #possible shift values #string - text #tuple(string1,string2,dict) - new tag with name string1,attrs = dict,and text string2 #dict and string2 are optional def __lshift__(self,val): if isinstance(val,basestring): self.__builder.data(val) else: self.__has_sub_tag = True assert hasattr(val,'__len__'),\ 'Shifted value should be tuple or list like object not %r' % val assert hasattr(val,'__getitem__'),\ 'Shifted value should be tuple or list like object not %r' % val name = val[0] if len(val) == 3: text = val[1] attrs = val[2] elif len(val) == 1: text = "" attrs = {} elif len(val) == 2: if isinstance(val[1],basestring): text = val[1] attrs = {} else: text = "" attrs = val[1] if self.__tab_level is not None: self.__builder.data("\n" + self.__tab_level) self.__builder.start(name, dict((k,str(v)) for k,v in attrs.items())) if text: self.__builder.data(text) self.__builder.end(name) return self # to allow xml << some1 << some2 << some3 #close builder def __invert__(self): if self.__node is not None: return self.__node self.__node = self.__builder.close() return self.__node def __str__(self): """return generated xml""" return tostring(~self,self.__encoding) def __unicode__(self): """return generated xml""" res = tostring(~self,self.__encoding) return res.decode(self.__encoding) #-------------------------------------------------------------------------------