382 lines
12 KiB
Python
382 lines
12 KiB
Python
"Convert"
|
|
import re
|
|
import StringIO, xml.dom.minidom
|
|
from trytond import pooler
|
|
from trytond.osv.fields import Integer
|
|
import csv
|
|
import os.path
|
|
from trytond.netsvc import Logger, LOG_ERROR, LOG_INFO, LocalService
|
|
from trytond.config import CONFIG
|
|
from trytond.version import VERSION
|
|
import logging
|
|
|
|
CDATA_START = re.compile('^\s*\<\!\[cdata\[', re.IGNORECASE)
|
|
CDATA_END = re.compile('\]\]\>\s*$', re.IGNORECASE)
|
|
|
|
# TODO :
|
|
# Add some exception helper, and give a good diagnostic for the errors
|
|
|
|
|
|
from xml import sax
|
|
|
|
|
|
class DummyTagHandler:
|
|
"""Dubhandler implementing empty methods. Will be used when whe
|
|
want to ignore the xml content"""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def startElement(self, name, attributes):
|
|
pass
|
|
|
|
def characters(self, data):
|
|
pass
|
|
|
|
def endElement(self, name):
|
|
pass
|
|
|
|
|
|
class MenuitemTagHandler:
|
|
"""Taghandler for the tag <record> """
|
|
def __init__(self, master_handler):
|
|
self.mh = master_handler
|
|
|
|
def startElement(self, name, attributes):
|
|
|
|
values = {}
|
|
|
|
self.xml_id = attributes['id'].encode('utf8')
|
|
|
|
for attr in ('name', 'icon', 'sequence', 'parent', 'action', 'groups'):
|
|
if attributes.get(attr):
|
|
values[attr] = attributes.get(attr).encode('utf8')
|
|
|
|
|
|
if values.get('parent'):
|
|
values['parent'] = self.mh.get_id(values['parent'])
|
|
|
|
action_name = False
|
|
if values.get('action'):
|
|
action_id = self.mh.get_id(values['action'])
|
|
|
|
# TODO maybe use a prefetch for this:
|
|
self.mh.cursor.execute(
|
|
"SELECT a.name, a.type, act.view_type, v.type " \
|
|
"FROM ir_action a " \
|
|
"LEFT JOIN ir_action_report report ON (a.id = report.action) " \
|
|
"LEFT JOIN ir_action_act_window act ON (a.id = act.action) " \
|
|
"LEFT JOIN ir_action_wizard wizard ON (a.id = wizard.action) " \
|
|
"LEFT JOIN ir_action_url url ON (a.id = url.action) " \
|
|
"LEFT JOIN ir_action_act_window_view wv on (act.id = wv.act_window) " \
|
|
"LEFT JOIN ir_ui_view v on (v.id = wv.view) " \
|
|
"WHERE report.id = %d " \
|
|
"OR act.id = %d " \
|
|
"OR wizard.id = %d " \
|
|
"OR url.id = %d " \
|
|
"ORDER by wv.sequence " \
|
|
"LIMIT 1", (action_id, action_id, action_id, action_id))
|
|
action_name, action_type, view_type, view_mode = \
|
|
self.mh.cursor.fetchone()
|
|
|
|
values['action'] = '%s,%d' % (action_type, action_id)
|
|
|
|
icon = attributes.get('icon', '').encode('utf8')
|
|
if icon:
|
|
values['icon'] = icon
|
|
elif action_type == 'ir.action.wizard':
|
|
values['icon'] = 'STOCK_EXECUTE'
|
|
elif action_type == 'ir.action.report':
|
|
values['icon'] = 'STOCK_PRINT'
|
|
elif action_type == 'ir.action.act_window':
|
|
if view_type == 'tree':
|
|
values['icon'] = 'STOCK_INDENT'
|
|
elif view_mode and view_mode.startswith('tree'):
|
|
values['icon'] = 'STOCK_JUSTIFY_FILL'
|
|
elif view_mode and view_mode.startswith('form'):
|
|
values['icon'] = 'STOCK_NEW'
|
|
elif view_mode and view_mode.startswith('graph'):
|
|
values['icon'] = 'terp-graph'
|
|
elif view_mode and view_mode.startswith('calendar'):
|
|
values['icon'] = 'terp-calendar'
|
|
else:
|
|
values['icon'] = 'STOCK_NEW'
|
|
|
|
if values.get('groups'):
|
|
g_names = values.split(',')
|
|
groups_value = []
|
|
for group in g_names:
|
|
if group.startswith('-'):
|
|
group_id = self.get_id(group[1:])
|
|
groups_value.append((3, group_id))
|
|
else:
|
|
group_id = self.get_id(group)
|
|
groups_value.append((4, group_id))
|
|
values['groups'] = groups_value
|
|
|
|
if not values.get('name'):
|
|
if not action_name:
|
|
raise Exception("Please provide at least a 'name' attributes "
|
|
"or a 'action' attributes on the menuitem tags.")
|
|
else:
|
|
values['name'] = action_name
|
|
|
|
self.values = values
|
|
|
|
|
|
def characters(self, data):
|
|
pass
|
|
|
|
def endElement(self, name):
|
|
"""Must return the object to use for the next call """
|
|
if name != "menuitem":
|
|
return self
|
|
else:
|
|
|
|
res = self.mh.pool.get('ir.model.data').import_record(
|
|
self.mh.cursor, self.mh.user,
|
|
'ir.ui.menu', self.mh.module, self.values, self.xml_id)
|
|
return None
|
|
|
|
def current_state(self):
|
|
return "Tag menuitem with id: %s"% \
|
|
(self.xml_id)
|
|
|
|
|
|
class RecordTagHandler:
|
|
|
|
"""Taghandler for the tag <record> and all the tags inside it"""
|
|
|
|
def __init__(self, master_handler):
|
|
# Remind reference of parent handler
|
|
self.mh = master_handler
|
|
|
|
|
|
def startElement(self, name, attributes):
|
|
|
|
# Manage the top level tag
|
|
if name == "record":
|
|
self.model = self.mh.pool.get(attributes["model"].encode('utf8'))
|
|
assert self.model, "The model %s does not exist !" % \
|
|
(attributes["model"].encode('utf8'),)
|
|
|
|
self.xml_id = attributes["id"].encode('utf8')
|
|
|
|
# create/update a dict containing fields values
|
|
self.values = {}
|
|
|
|
self.current_field = None
|
|
self.cdata = False
|
|
|
|
return self.xml_id
|
|
|
|
# Manage included tags:
|
|
elif name == "field":
|
|
|
|
field_name = attributes['name'].encode('utf8')
|
|
field_type = attributes.get('type', '').encode('utf8')
|
|
# Create a new entry in the values
|
|
self.values[field_name] = ""
|
|
# Remind the current name (see characters)
|
|
self.current_field = field_name
|
|
# Put a flag to escape cdata tags
|
|
if field_type == "xml":
|
|
self.cdata = "start"
|
|
|
|
# Catch the known attributes
|
|
search_attr = attributes.get('search','').encode('utf8')
|
|
ref_attr = attributes.get('ref', '').encode('utf8')
|
|
eval_attr = attributes.get('eval', '').encode('utf8')
|
|
|
|
if search_attr:
|
|
if attributes.get('model', ''):
|
|
search_model = attributes['model'].encode('utf8')
|
|
else:
|
|
search_model = self.model._columns[field_name]._obj
|
|
f_obj = self.mh.pool.get(search_model)
|
|
answer = f_obj.browse(
|
|
self.mh.cursor, self.mh.user,
|
|
f_obj.search(self.mh.cursor, self.mh.user, eval(search_attr)))
|
|
|
|
if not answer: return
|
|
|
|
if self.model._columns[field_name]._type == 'many2many':
|
|
self.values[field_name] = [(6, 0, [x['id'] for x in answer])]
|
|
|
|
elif self.model._columns[field_name]._type == 'many2one':
|
|
self.values[field_name] = answer[0]['id']
|
|
|
|
elif ref_attr:
|
|
self.values[field_name] = self.mh.get_id(ref_attr)
|
|
|
|
elif eval_attr:
|
|
|
|
import time
|
|
context = {}
|
|
context['time'] = time
|
|
context['version'] = VERSION.rsplit('.', 1)[0]
|
|
context['ref'] = self.mh.get_id
|
|
context['obj'] = lambda *a: 1
|
|
self.values[field_name] = eval(eval_attr, context)
|
|
|
|
else:
|
|
raise Exception("Tags '%s' not supported inside tag record."% (name,))
|
|
|
|
def characters(self, data):
|
|
|
|
"""If whe are in a field tag, consume all the content"""
|
|
|
|
if not self.current_field:
|
|
return
|
|
# Escape start cdata tag if necessary
|
|
if self.cdata == "start":
|
|
data = CDATA_START.sub('', data)
|
|
self.start_cdata = "inside"
|
|
|
|
self.values[self.current_field] += data.encode('utf8')
|
|
|
|
|
|
def endElement(self, name):
|
|
|
|
"""Must return the object to use for the next call, if name is
|
|
not 'record' we return self to keep our hand on the
|
|
process. If name is 'record' we return None to end the
|
|
delegation"""
|
|
|
|
if name == "field":
|
|
if not self.current_field:
|
|
raise Exception("Application error"
|
|
"current_field expected to be set.")
|
|
# Escape end cdata tag :
|
|
if self.cdata in ('inside', 'start'):
|
|
self.values[self.current_field] =\
|
|
CDATA_END.sub('', self.values[self.current_field])
|
|
self.cdata = 'done'
|
|
|
|
value = self.values[self.current_field]
|
|
match = re.findall('[^%]%\((.*?)\)[ds]', value)
|
|
xml_ids = {}
|
|
for xml_id in match:
|
|
xml_ids[xml_id] = self.mh.get_id(xml_id)
|
|
self.values[self.current_field] = value % xml_ids
|
|
|
|
self.current_field = None
|
|
return self
|
|
|
|
elif name == "record":
|
|
res = self.mh.pool.get('ir.model.data').import_record(
|
|
self.mh.cursor, self.mh.user,
|
|
self.model._name, self.mh.module, self.values, self.xml_id)
|
|
|
|
return None
|
|
else:
|
|
raise Exception("Unexpected closing tag '%s'"% (name,))
|
|
|
|
def current_state(self):
|
|
return "Tag record for model %s with id: %s"% \
|
|
(self.model and self.model._name or "?", self.xml_id)
|
|
|
|
class TrytondXmlHandler(sax.handler.ContentHandler):
|
|
|
|
def __init__(self, cursor, pool, module,):
|
|
"Register known taghandlers, and managed tags."
|
|
|
|
self.pool = pool
|
|
self.cursor = cursor
|
|
self.user = 0
|
|
self.module = module
|
|
|
|
|
|
# Tag handlders are used to delegate the processing
|
|
self.taghandlerlist = {
|
|
'record': RecordTagHandler(self),
|
|
'menuitem': MenuitemTagHandler(self),
|
|
}
|
|
self.taghandler = None
|
|
|
|
# Managed tags are handled by the current class
|
|
self.managedtags= ["data", "tryton"]
|
|
|
|
|
|
def get_id(self, xml_id):
|
|
|
|
module = self.module
|
|
if '.' in xml_id:
|
|
module, xml_id = xml_id.split('.')
|
|
|
|
return self.pool.get('ir.model.data').get_id(
|
|
self.cursor, self.user, module, xml_id)
|
|
|
|
def startElement(self, name, attributes):
|
|
"""Rebind the current handler if necessary and call
|
|
startElement on it"""
|
|
|
|
if not self.taghandler:
|
|
|
|
if name in self.taghandlerlist:
|
|
self.taghandler = self.taghandlerlist[name]
|
|
self.taghandler.startElement(name, attributes)
|
|
|
|
elif name == "data":
|
|
self.noupdate = attributes.get("noupdate", False)
|
|
|
|
elif name == "tryton":
|
|
pass
|
|
|
|
else:
|
|
Logger().notify_channel("init", LOG_INFO,
|
|
"Tag "+ name + " not supported")
|
|
return
|
|
else:
|
|
self.taghandler.startElement(name, attributes)
|
|
|
|
def characters(self, data):
|
|
if self.taghandler:
|
|
self.taghandler.characters(data)
|
|
|
|
def endElement(self, name):
|
|
|
|
# Closing tag found, if we are in a delegation the handler
|
|
# know what to do:
|
|
if self.taghandler:
|
|
self.taghandler = self.taghandler.endElement(name)
|
|
|
|
def current_state(self):
|
|
if self.taghandler:
|
|
return self.taghandler.current_state()
|
|
else:
|
|
return "In top level tag"
|
|
|
|
def convert_xml_import_sax(cursor, module, xmlstream):
|
|
|
|
parser = sax.make_parser()
|
|
# Tell the parser we are not interested in XML namespaces
|
|
parser.setFeature(sax.handler.feature_namespaces, 0)
|
|
|
|
handler = TrytondXmlHandler(
|
|
cursor=cursor,
|
|
pool=pooler.get_pool(cursor.dbname),
|
|
module=module,
|
|
)
|
|
|
|
parser.setContentHandler(handler)
|
|
source = sax.InputSource()
|
|
source.setByteStream(xmlstream)
|
|
|
|
try:
|
|
parser.parse(source)
|
|
except:
|
|
Logger().notify_channel(
|
|
"init", LOG_ERROR,
|
|
"Error while parsing xml file. Current state is:\n" +\
|
|
handler.current_state()
|
|
)
|
|
|
|
raise
|
|
|
|
return True
|
|
|
|
|
|
# use convert_xml_import_sax or convert_xml_import_dom
|
|
convert_xml_import = convert_xml_import_sax
|