From 8e13209525e153730826a7fbee9997ff5387f4a1 Mon Sep 17 00:00:00 2001 From: OpenERP Framework R&D Team <> Date: Thu, 20 May 2010 16:35:28 +0200 Subject: [PATCH] [ADD] new experimental utility script: xml2yml for conversion of XML data/test files to the new YAML format bzr revid: odo@openerp.com-20100520143528-3717ybwyp1exhsd6 --- doc/Changelog-6.x | 3 +- tools/xml2yml.py | 345 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 347 insertions(+), 1 deletion(-) create mode 100755 tools/xml2yml.py diff --git a/doc/Changelog-6.x b/doc/Changelog-6.x index 78d65c8e809..05b5f58217e 100644 --- a/doc/Changelog-6.x +++ b/doc/Changelog-6.x @@ -4,6 +4,7 @@ Improvements (server) --------------------- - * support of 'ref' attribute for importing 'reference' field values, as for many2one fields. + * support of 'ref' attribute for importing 'reference' field values, as for many2one fields. + * experimental xml2yml script in /tools for conversion of XML data/test files to the new YAML format diff --git a/tools/xml2yml.py b/tools/xml2yml.py new file mode 100755 index 00000000000..e926ddf5f94 --- /dev/null +++ b/tools/xml2yml.py @@ -0,0 +1,345 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 OpenERP SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +""" +Experimental script for conversion between OpenERP's XML serialization format +and the new YAML serialization format introduced in OpenERP 6.0. +Intended to be used as a quick preprocessor for converting data/test files, then +to be fine-tuned manually. +""" + +import yaml +import logging +from lxml import etree + +__VERSION__ = '0.0.2' + +def toString(value): + value='"' + value + '"' + return value + +class XmlTag(etree.ElementBase): + def _to_yaml(self): + child_tags = [] + for node in self: + if hasattr(node, '_to_yaml'): + child_tags.append(node._to_yaml()) + return self.tag(attrib=self.attrib, child_tags=child_tags) + +class YamlTag(object): + """ + Superclass for constructors of custom tags defined in yaml file. + """ + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + self.attrib = self.__dict__.get('attrib', {}) + self.child_tags = self.__dict__.get('child_tags', '') + def __getitem__(self, key): + return getattr(self, key) + def __getattr__(self, attr): + return None + def __repr__(self): + for k,v in self.attrib.iteritems(): + if str(v) and str(v)[0] in ['[', '{', '#', '*', '(']: + self.attrib[k] = toString(self.attrib[k]).replace("'", '') + st = self.yaml_tag + ' ' + str(self.attrib) + return st + +# attrib tags +class ref(YamlTag): + yaml_tag = u'!ref' + def __init__(self, expr="False"): + self.expr = expr + def __repr__(self): + return "'%s'"%str(self.expr) + +class Eval(YamlTag): + yaml_tag = u'!eval' + def __init__(self, expr="False"): + self.expr = expr + def __repr__(self): + value=str(self.expr) + if value.find("6,") != -1: + value = eval(str(eval(value))) + value=value[0][2] + value = [[value]] + else: + try: + value=int(value) + except: + if value and value[0] in ['[', '{', '#', '*', '(']: + value = value.replace('"', r'\"') + value = toString(value) + else: + try: + value = eval(value) + except Exception: + pass + return value + +class Search(YamlTag): + yaml_tag = u'!ref' + +# test tag +class xml_test(XmlTag): + def _to_yaml(self): + expr = self.attrib.get('expr') + text = self.text + if text: + expr = expr + ' == ' + '"%s"'%text + return [[expr]] + +class xml_data(etree.ElementBase): + def _to_yaml(self): + value = self.attrib.get('noupdate', "0") + return data(value) + +# field tag: +class xml_field(etree.ElementBase): + def _to_yaml(self): + field = ' ' + self.attrib.pop('name','unknown') + + if self.attrib.get('search', None): + value = Search(attrib=self.attrib, child_tags='').__repr__() + else: + attr = (self.attrib.get('ref', None) and 'ref') or (self.attrib.get('eval', None) and 'eval') or 'None' + value = Eval(self.attrib.get(attr, self.text)).__repr__() or '' + return {field: value} + +# value tag +class xml_value(etree.ElementBase): + def _to_yaml(self): + + if self.attrib.get('eval', None): + key, val = 'eval', '"'+self.attrib.get('eval')+'"' + elif self.attrib.get('model', None): + key, val = 'model', self.attrib.get('model') + val=val.replace("'",'""') + self.attrib.pop(key) + d={} + for k,v in self.attrib.iteritems(): + if k == 'search': + v = '"' + v + '"' + k='--' + k + v=v.replace("'",'""') + d[k] = v + if d: + ls=[[{key:val},dict(d)]] + else: + ls=[[{key:val}]] + return ls + +# data tag +class data(YamlTag): + yaml_tag = u'!context' + def __init__(self, noupdate="0"): + self.child_tags = {' noupdate':noupdate} + def __repr__(self): + return "!!context" + +# Record tag +class Record(YamlTag): + yaml_tag = u'!record' +class xml_record(XmlTag): + tag=Record + def _to_yaml(self): + child_tags = {} + for node in self: + if hasattr(node, '_to_yaml'): + child_tags.update(node._to_yaml()) + return Record(attrib=self.attrib, child_tags=child_tags) + +# ir_set tag +class Ir_Set(YamlTag): + yaml_tag = u'!ir_set' + def __repr__(self): + st = self.yaml_tag + return st +class xml_ir_set(XmlTag): + tag=Ir_Set + def _to_yaml(self): + child_tags = {} + for node in self: + if hasattr(node, '_to_yaml'): + child_tags.update(node._to_yaml()) + return Ir_Set(attrib=self.attrib, child_tags=child_tags) + +# workflow tag +class Workflow(YamlTag): + yaml_tag = u'!workflow' +class xml_workflow(XmlTag): + tag=Workflow + +# function tag +class Function(YamlTag): + yaml_tag = u'!function' +class xml_function(XmlTag): + tag=Function + +# function tag +class Assert(YamlTag): + yaml_tag = u'!assert' +class xml_assert(XmlTag): + tag=Assert + +# menuitem tagresult.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",'')) +class MenuItem(YamlTag): + yaml_tag = u'!menuitem' +class xml_menuitem(XmlTag): + tag=MenuItem + +# act_window tag +class ActWindow(YamlTag): + yaml_tag = u'!act_window' +class xml_act_window(XmlTag): + tag=ActWindow + +# report tag +class Report(YamlTag): + yaml_tag = u'!report' +class xml_report(XmlTag): + tag=Report + +# deletes tag +class Delete(YamlTag): + yaml_tag = u'!delete' +class xml_delete(XmlTag): + tag=Delete + +# python tag +class Python(YamlTag): + yaml_tag = u'!python' +class xml_python(XmlTag): + tag=Python + +# context tag +class Context(YamlTag): + yaml_tag = u'!context' +class xml_context(XmlTag): + tag=Context + +# url tag +class Url(YamlTag): + yaml_tag = u'!url' +class xml_url(XmlTag): + tag=Url + +# delete tag +class Delete(YamlTag): + yaml_tag = u'!delete' +class xml_delete(XmlTag): + tag=Delete + +def represent_data(dumper, data): + return dumper.represent_mapping(u'tag:yaml.org,2002:map', [('!'+str(data), data.child_tags)]) + +yaml.SafeDumper.add_representer(Record, represent_data) +yaml.SafeDumper.add_representer(data, represent_data) +yaml.SafeDumper.add_representer(Workflow, represent_data) +yaml.SafeDumper.add_representer(Function, represent_data) +yaml.SafeDumper.add_representer(Assert, represent_data) +yaml.SafeDumper.add_representer(MenuItem, represent_data) +yaml.SafeDumper.add_representer(Ir_Set, represent_data) +yaml.SafeDumper.add_representer(Python, represent_data) +yaml.SafeDumper.add_representer(Context, represent_data) + +class MyLookup(etree.CustomElementClassLookup): + def lookup(self, node_type, document, namespace, name): + if node_type=='element': + return { + 'data': xml_data, + 'record': xml_record, + 'field': xml_field, + 'workflow': xml_workflow, + 'function': xml_function, + 'value': xml_value, + 'assert': xml_assert, + 'test': xml_test, + 'menuitem': xml_menuitem, + 'act_window': xml_act_window, + 'report': xml_report, + 'delete': xml_delete, + 'python': xml_python, + 'context': xml_context, + 'url': xml_url, + 'ir_set': xml_ir_set, + }.get(name, None) + elif node_type=='comment': + return None#xml_comment + return None + +class xml_parse(object): + def __init__(self): + self.context = {} + def parse(self, fname): + parser = etree.XMLParser() + parser.setElementClassLookup(MyLookup()) + result = [] + self.root = etree.XML(file(fname).read(), parser) + for data in self.root: + if hasattr(data, '_to_yaml'): + obj = data._to_yaml() + if obj.yaml_tag == '!context': + result.append(yaml.dump(str(obj)).replace("'",'').split('\n')[0]) + result.append(yaml.dump(obj.child_tags, default_flow_style=False).replace("'",'')) + else: + result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True).replace("'",'')) + self.context.update(data.attrib) + for tag in data: + if tag.tag == etree.Comment: + result.append(tag) + else: + if hasattr(tag, '_to_yaml'): + obj = tag._to_yaml() + if not obj.child_tags: + result.append(yaml.dump('!'+str(obj), default_flow_style=False, allow_unicode=True, width=999).replace("'",'')) + else: + result.append(yaml.safe_dump(obj, default_flow_style=False, allow_unicode=True, width=999).replace('\n:', ':\n').replace("'",'')) + print "# Experimental OpenERP xml-to-yml conversion! (v%s)"%__VERSION__ + print "# Please use this as a first conversion/preprocessing step," + print "# not as a production-ready tool!" + for record in result: + if type(record) != type(''): + record=str(record) + l= record.split("\n") + for line in l: + print '#' + str(line) + continue + record=str(record) + record = record.replace('- --',' ') #for value tag + record = record.replace('!!', '- \n !') #for all parent tags + record = record.replace('- - -', ' -') #for many2many fields + record = record.replace('? ', '') #for long expressions + record = record.replace('""', "'") #for string-value under value tag + print record + +if __name__=='__main__': + import optparse + import sys + parser = optparse.OptionParser( + usage = '%s file.xml' % sys.argv[0]) + (opt, args) = parser.parse_args() + if len(args) != 1: + parser.error("incorrect number of arguments") + fname = sys.argv[1] + p = xml_parse() + p.parse(fname) +