[MERGE] from trunk
[odoo/odoo.git] / history / xml2yml.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 OpenERP SA (<http://openerp.com>).
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20 ##############################################################################
21
22 """
23 Experimental script for conversion between OpenERP's XML serialization format
24 and the new YAML serialization format introduced in OpenERP 6.0.
25 Intended to be used as a quick preprocessor for converting data/test files, then
26 to be fine-tuned manually.
27 """
28
29 import yaml
30 import logging
31 from lxml import etree
32
33 __VERSION__ = '0.0.2'
34
35 def toString(value):
36     value='"' + value + '"'
37     return value
38
39 class XmlTag(etree.ElementBase):
40     def _to_yaml(self):
41         child_tags = []
42         for node in self:
43             if hasattr(node, '_to_yaml'):
44                 child_tags.append(node._to_yaml())
45         return self.tag(attrib=self.attrib, child_tags=child_tags)
46
47 class YamlTag(object):
48     """
49     Superclass for constructors of custom tags defined in yaml file.
50     """
51     def __init__(self, **kwargs):
52         self.__dict__.update(kwargs)
53         self.attrib = self.__dict__.get('attrib', {})
54         self.child_tags = self.__dict__.get('child_tags', '')
55     def __getitem__(self, key):
56         return getattr(self, key)
57     def __getattr__(self, attr):
58         return None
59     def __repr__(self):
60         for k,v in self.attrib.iteritems():
61             if str(v) and str(v)[0] in ['[', '{', '#', '*', '(']:
62                 self.attrib[k] = toString(self.attrib[k]).replace("'", '')
63         st = self.yaml_tag + ' ' + str(self.attrib)
64         return st
65
66 # attrib tags
67 class ref(YamlTag):
68     yaml_tag = u'!ref'
69     def __init__(self, expr="False"):
70         self.expr = expr
71     def __repr__(self):
72         return "'%s'"%str(self.expr)
73
74 class Eval(YamlTag):
75     yaml_tag = u'!eval'
76     def __init__(self, expr="False"):
77         self.expr = expr
78     def __repr__(self):
79         value=str(self.expr)
80         if value.find("6,") != -1:
81             value = eval(str(eval(value)))
82             value=value[0][2]
83             value = [[value]]
84         else:
85             try:
86                 value=int(value)
87             except:
88                 if value and value[0] in ['[', '{', '#', '*', '(']:
89                     value = value.replace('"', r'\"')
90                     value = toString(value)
91                 else:
92                     try:
93                         value = eval(value)
94                     except Exception:
95                         pass
96         return value
97
98 class Search(YamlTag):
99     yaml_tag = u'!ref'
100
101 # test tag
102 class xml_test(XmlTag):
103     def _to_yaml(self):
104         expr = self.attrib.get('expr')
105         text = self.text
106         if text:
107             expr = expr + ' == ' + '"%s"'%text
108         return [[expr]]
109
110 class xml_data(etree.ElementBase):
111     def _to_yaml(self):
112         value = self.attrib.get('noupdate', "0")
113         return data(value)
114
115 # field tag:
116 class xml_field(etree.ElementBase):
117     def _to_yaml(self):
118         field = '  ' + self.attrib.pop('name','unknown')
119
120         if self.attrib.get('search', None):
121             value = Search(attrib=self.attrib, child_tags='').__repr__()
122         else:
123             attr = (self.attrib.get('ref', None) and 'ref') or (self.attrib.get('eval', None) and 'eval') or 'None'
124             value = Eval(self.attrib.get(attr, self.text)).__repr__() or ''
125         return {field: value}
126
127 # value tag
128 class xml_value(etree.ElementBase):
129     def _to_yaml(self):
130
131         if self.attrib.get('eval', None):
132             key, val = 'eval', '"'+self.attrib.get('eval')+'"'
133         elif self.attrib.get('model', None):
134             key, val = 'model', self.attrib.get('model')
135         val=val.replace("'",'""')
136         self.attrib.pop(key)
137         d={}
138         for k,v in self.attrib.iteritems():
139             if k == 'search':
140                 v = '"' + v + '"'
141             k='--' + k
142             v=v.replace("'",'""')
143             d[k] = v
144         if d:
145             ls=[[{key:val},dict(d)]]
146         else:
147             ls=[[{key:val}]]
148         return ls
149
150 # data tag
151 class data(YamlTag):
152     yaml_tag = u'!context'
153     def __init__(self, noupdate="0"):
154         self.child_tags = {'    noupdate':noupdate}
155     def __repr__(self):
156         return "!!context"
157
158 # Record tag
159 class Record(YamlTag):
160     yaml_tag = u'!record'
161 class xml_record(XmlTag):
162     tag=Record
163     def _to_yaml(self):
164         child_tags = {}
165         for node in self:
166             if hasattr(node, '_to_yaml'):
167                 child_tags.update(node._to_yaml())
168         return Record(attrib=self.attrib, child_tags=child_tags)
169
170 # ir_set tag
171 class Ir_Set(YamlTag):
172     yaml_tag = u'!ir_set'
173     def __repr__(self):
174         st = self.yaml_tag
175         return st
176 class xml_ir_set(XmlTag):
177     tag=Ir_Set
178     def _to_yaml(self):
179         child_tags = {}
180         for node in self:
181             if hasattr(node, '_to_yaml'):
182                 child_tags.update(node._to_yaml())
183         return Ir_Set(attrib=self.attrib, child_tags=child_tags)
184
185 # workflow tag
186 class Workflow(YamlTag):
187     yaml_tag = u'!workflow'
188 class xml_workflow(XmlTag):
189     tag=Workflow
190
191 # function tag
192 class Function(YamlTag):
193     yaml_tag = u'!function'
194 class xml_function(XmlTag):
195     tag=Function
196
197 # function tag
198 class Assert(YamlTag):
199     yaml_tag = u'!assert'
200 class xml_assert(XmlTag):
201     tag=Assert
202
203 # menuitem tagresult.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",''))
204 class MenuItem(YamlTag):
205     yaml_tag = u'!menuitem'
206 class xml_menuitem(XmlTag):
207     tag=MenuItem
208
209 # act_window tag
210 class ActWindow(YamlTag):
211     yaml_tag = u'!act_window'
212 class xml_act_window(XmlTag):
213     tag=ActWindow
214
215 # report tag
216 class Report(YamlTag):
217     yaml_tag = u'!report'
218 class xml_report(XmlTag):
219     tag=Report
220
221 # deletes tag
222 class Delete(YamlTag):
223     yaml_tag = u'!delete'
224 class xml_delete(XmlTag):
225     tag=Delete
226
227 # python tag
228 class Python(YamlTag):
229     yaml_tag = u'!python'
230 class xml_python(XmlTag):
231     tag=Python
232
233 # context tag
234 class Context(YamlTag):
235     yaml_tag = u'!context'
236 class xml_context(XmlTag):
237     tag=Context
238
239 # url tag
240 class Url(YamlTag):
241     yaml_tag = u'!url'
242 class xml_url(XmlTag):
243     tag=Url
244
245 # delete tag
246 class Delete(YamlTag):
247     yaml_tag = u'!delete'
248 class xml_delete(XmlTag):
249     tag=Delete
250
251 def represent_data(dumper, data):
252         return dumper.represent_mapping(u'tag:yaml.org,2002:map', [('!'+str(data), data.child_tags)])
253
254 yaml.SafeDumper.add_representer(Record, represent_data)
255 yaml.SafeDumper.add_representer(data, represent_data)
256 yaml.SafeDumper.add_representer(Workflow, represent_data)
257 yaml.SafeDumper.add_representer(Function, represent_data)
258 yaml.SafeDumper.add_representer(Assert, represent_data)
259 yaml.SafeDumper.add_representer(MenuItem, represent_data)
260 yaml.SafeDumper.add_representer(Ir_Set, represent_data)
261 yaml.SafeDumper.add_representer(Python, represent_data)
262 yaml.SafeDumper.add_representer(Context, represent_data)
263
264 class MyLookup(etree.CustomElementClassLookup):
265     def lookup(self, node_type, document, namespace, name):
266         if node_type=='element':
267             return {
268                 'data': xml_data,
269                 'record': xml_record,
270                 'field': xml_field,
271                 'workflow': xml_workflow,
272                 'function': xml_function,
273                 'value': xml_value,
274                 'assert': xml_assert,
275                 'test': xml_test,
276                 'menuitem': xml_menuitem,
277                 'act_window': xml_act_window,
278                 'report': xml_report,
279                 'delete': xml_delete,
280                 'python': xml_python,
281                 'context': xml_context,
282                 'url': xml_url,
283                 'ir_set': xml_ir_set,
284             }.get(name, None)
285         elif node_type=='comment':
286             return None#xml_comment
287         return None
288
289 class xml_parse(object):
290     def __init__(self):
291         self.context = {}
292     def parse(self, fname):
293         parser = etree.XMLParser()
294         parser.setElementClassLookup(MyLookup())
295         result = []
296         self.root = etree.XML(file(fname).read(), parser)
297         for data in self.root:
298             if hasattr(data, '_to_yaml'):
299                 obj = data._to_yaml()
300                 if obj.yaml_tag == '!context':
301                     result.append(yaml.dump(str(obj)).replace("'",'').split('\n')[0])
302                     result.append(yaml.dump(obj.child_tags, default_flow_style=False).replace("'",''))
303                 else:
304                     result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",''))
305             self.context.update(data.attrib)
306             for tag in data:
307                 if tag.tag == etree.Comment:
308                     result.append(tag)
309                 else:
310                     if hasattr(tag, '_to_yaml'):
311                         obj = tag._to_yaml()
312                         if not obj.child_tags:
313                             result.append(yaml.dump('!'+str(obj), default_flow_style=False, allow_unicode=True, width=999).replace("'",''))
314                         else:
315                             result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True, width=999).replace('\n:', ':\n').replace("'",''))
316         print "# Experimental OpenERP xml-to-yml conversion! (v%s)"%__VERSION__
317         print "# Please use this as a first conversion/preprocessing step,"
318         print "# not as a production-ready tool!"
319         for record in result:
320             if type(record) != type(''):
321                 record=str(record)
322                 l= record.split("\n")
323                 for line in l:
324                     print '#' + str(line)
325                 continue
326             record=str(record)
327             record = record.replace('- --','  ')        #for value tag
328             record = record.replace('!!', '- \n  !')    #for all parent tags
329             record = record.replace('- - -', '    -')   #for many2many fields
330             record = record.replace('? ', '')           #for long expressions
331             record = record.replace('""', "'")          #for string-value under value tag
332             print record
333
334 if __name__=='__main__':
335     import optparse
336     import sys
337     parser = optparse.OptionParser(
338         usage = '%s file.xml' % sys.argv[0])
339     (opt, args) = parser.parse_args()
340     if len(args) != 1:
341         parser.error("incorrect number of arguments")
342     fname = sys.argv[1]
343     p = xml_parse()
344     p.parse(fname)
345
346
347 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: