1be17b0fba9b7772491e19535ea9bf36f80073ba
[sfa.git] / xmlbuilder-0.9 / xmlbuilder / __init__.py
1 #!/usr/bin/env python\r
2 #-------------------------------------------------------------------------------\r
3 from __future__ import with_statement\r
4 #-------------------------------------------------------------------------------\r
5 from xml.etree.ElementTree import TreeBuilder,tostring\r
6 #-------------------------------------------------------------------------------\r
7 __all__ = ["XMLBuilder"]\r
8 __doc__ = """\r
9 XMLBuilder is simple library build on top of ElementTree.TreeBuilder to\r
10 simplify xml files creation as much as possible. Althow it can produce\r
11 structured result with identated child tags. `XMLBuilder` use python `with`\r
12 statement to define xml tag levels and `<<` operator for simple cases -\r
13 text and tag without childs.\r
14 \r
15 from __future__ import with_statement\r
16 from xmlbuilder import XMLBuilder\r
17 x = XMLBuilder(format=True)\r
18 with x.root(a = 1):\r
19     with x.data:\r
20         [x << ('node',{'val':i}) for i in range(10)]\r
21 \r
22 etree_node = ~x\r
23 print str(x)\r
24 """\r
25 #-------------------------------------------------------------------------------\r
26 class _XMLNode(object):\r
27     """Class for internal usage"""\r
28     def __init__(self,parent,name,builder):\r
29         self.builder = builder\r
30         self.name = name\r
31         self.text = []\r
32         self.attrs = {}\r
33         self.entered = False\r
34         self.parent = parent\r
35     def __call__(self,*dt,**mp):\r
36         text = "".join(dt)\r
37         if self.entered:\r
38             self.builder.data(text)\r
39         else:\r
40             self.text.append(text)\r
41         if self.entered:\r
42             raise ValueError("Can't add attributes to already opened element")\r
43         smp = dict((k,str(v)) for k,v in mp.items())\r
44         self.attrs.update(smp)\r
45         return self\r
46     def __enter__(self):\r
47         self.parent += 1\r
48         self.builder.start(self.name,self.attrs)\r
49         self.builder.data("".join(self.text))\r
50         self.entered = True\r
51         return self\r
52     def __exit__(self,x,y,z):\r
53         self.parent -= 1\r
54         self.builder.end(self.name)\r
55         return False\r
56 #-------------------------------------------------------------------------------\r
57 class XMLBuilder(object):\r
58     """XmlBuilder(encoding = 'utf-8', # result xml file encoding\r
59             builder = None, #etree.TreeBuilder or compatible class\r
60             tab_level = None, #current tabulation level - string\r
61             format = False,   # make formatted output\r
62             tab_step = " " * 4) # tabulation step\r
63     use str(builder) or unicode(builder) to get xml text or\r
64     ~builder to obtaine etree.ElementTree\r
65     """\r
66     def __init__(self,encoding = 'utf-8',\r
67                       builder = None,\r
68                       tab_level = None,\r
69                       format = False,\r
70                       tab_step = " " * 4):\r
71         self.__builder = builder or TreeBuilder()\r
72         self.__encoding = encoding \r
73         if format :\r
74             if tab_level is None:\r
75                 tab_level = ""\r
76         if tab_level is not None:\r
77             if not format:\r
78                 raise ValueError("format is False, but tab_level not None")\r
79         self.__tab_level = tab_level # current format level\r
80         self.__tab_step = tab_step   # format step\r
81         self.__has_sub_tag = False   # True, if current tag had childrens\r
82         self.__node = None\r
83     # called from _XMLNode when tag opened\r
84     def __iadd__(self,val):\r
85         self.__has_sub_tag = False\r
86         if self.__tab_level is not None:\r
87             self.__builder.data("\n" + self.__tab_level)\r
88             self.__tab_level += self.__tab_step\r
89         return self\r
90     # called from XMLNode when tag closed\r
91     def __isub__(self,val):\r
92         if self.__tab_level is not None:\r
93             self.__tab_level = self.__tab_level[:-len(self.__tab_step)]\r
94             if self.__has_sub_tag:\r
95                 self.__builder.data("\n" + self.__tab_level)\r
96         self.__has_sub_tag = True\r
97         return self\r
98     def __getattr__(self,name):\r
99         return _XMLNode(self,name,self.__builder)\r
100     def __call__(self,name,*dt,**mp):\r
101         x = _XMLNode(self,name,self.__builder)\r
102         x(*dt,**mp)\r
103         return x\r
104     #create new tag or add text\r
105     #possible shift values\r
106     #string - text\r
107     #tuple(string1,string2,dict) - new tag with name string1,attrs = dict,and text string2\r
108     #dict and string2 are optional\r
109     def __lshift__(self,val):\r
110         if isinstance(val,basestring):\r
111             self.__builder.data(val)\r
112         else:\r
113             self.__has_sub_tag = True\r
114             assert hasattr(val,'__len__'),\\r
115                 'Shifted value should be tuple or list like object not %r' % val\r
116             assert hasattr(val,'__getitem__'),\\r
117                 'Shifted value should be tuple or list like object not %r' % val\r
118             name = val[0]\r
119             if len(val) == 3:\r
120                 text = val[1]\r
121                 attrs = val[2]\r
122             elif len(val) == 1:\r
123                 text = ""\r
124                 attrs = {}\r
125             elif len(val) == 2:\r
126                 if isinstance(val[1],basestring):\r
127                     text = val[1]\r
128                     attrs = {}\r
129                 else:\r
130                     text = ""\r
131                     attrs = val[1]\r
132             if self.__tab_level is not None:\r
133                 self.__builder.data("\n" + self.__tab_level)\r
134             self.__builder.start(name,\r
135                                  dict((k,str(v)) for k,v in attrs.items()))\r
136             if text:\r
137                 self.__builder.data(text)\r
138             self.__builder.end(name)\r
139         return self # to allow xml << some1 << some2 << some3\r
140     #close builder\r
141     def __invert__(self):\r
142         if self.__node is not None:\r
143             return self.__node\r
144         self.__node = self.__builder.close()\r
145         return self.__node\r
146     def __str__(self):\r
147         """return generated xml"""\r
148         return tostring(~self,self.__encoding)\r
149     def __unicode__(self):\r
150         """return generated xml"""\r
151         res = tostring(~self,self.__encoding)\r
152         return res.decode(self.__encoding)\r
153 #-------------------------------------------------------------------------------\r