No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

xmlfile.py 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. #-*- coding: utf-8 -*-
  2. import lxml
  3. import os
  4. from lxml import etree
  5. from lodel.context import LodelContext
  6. LodelContext.expose_modules(globals(), {
  7. 'lodel.editorial_model.model': ['EditorialModel'],
  8. 'lodel.editorial_model.components': ['EmComponent', 'EmClass', 'EmField',
  9. 'EmGroup'],
  10. 'lodel.utils.mlstring': ['MlString']})
  11. ##@package lodel.editorial_model.translator.xmlfile Translator module designed
  12. #to load & save EM in XML
  13. #
  14. # Structure of a xml file which represents an editorial model:
  15. # <ul>
  16. # <li>\<name\>: name of the model, field <b><em>name</em></b> in class <b><em>EditorialModel</em></b>
  17. # <li>\<description\>: field <b><em>description</em></b> of a composed element, one for each language translation named
  18. # <ul>
  19. # <li>\<fre\> for french,
  20. # <li>\<eng\> for english,
  21. # <li>\<esp\> for spanish,
  22. # <li>\<ger\> for german
  23. # </ul>
  24. # <li>\<classes\>: set of all <b><em>EmClass</em></b> in the model \n
  25. # for each classe: \n
  26. # \<class\><ul>
  27. # <li>\<uid\>the class's id
  28. # <li>\<display_name\> The name of the class, field <b><em>display_name</em></b> of the <b><em>EmClass</em></b> , in different languages if they're available :
  29. # <ul><li>\<fre\> for french,
  30. # <li>\<eng\> for english,
  31. # <li>\<esp\> for spanish,
  32. # <li>\<ger> for german</ul>
  33. # <li>\<help_text\> Short explanation of the class's purpose, in different languages, as above
  34. # <li>\<abstract\> True or False, field <b><em>abstract</em></b> of the <b><em>EmClass</em></b>
  35. # <li>\<pure_abstract\> True or False, field <b><em>pure_bastract</em></b> of the <b><em>EmClass</em></b>
  36. # <li>\<group\><b><em>uid</em></b> of the group of the field <b><em>group</em></b> of the <b><em>EmClass</em></b>
  37. # <li>\<fields\>: set of all the <b><em>EmField</em></b> of the <b><em>EmClass</em></b>\n
  38. # for each field: \n
  39. # \<field\>
  40. # <ul><li>\<uid\> uid of the <b><em>EmField</em></b>
  41. # <li>\<display_name\> field <b><em>display_name</em></b> of the <b><em>EmField</em></b>, in different languages, as above
  42. # <li>\<help_text\> Short explanation of the class's purpose, in different languages, as above
  43. # <li>\<group\><b><em>uid</em></b> of the group of the field <b><em>group</em></b> of the <b><em>EmClass</em></b>
  44. # <li>\<datahandler_name\> field <b><em>datahandler_name</em></b> of the Emfield, the name of a datahandler
  45. # <li>\<datahandler_options\>, a list of xml items, each of them named with an option name and contains its value</ul>
  46. # </ul>
  47. # <li>\<groups\>: set of all the groups <b><em>EmGroup</em></b> in the model\n
  48. # for each group:\n
  49. # <ul><li>\<uid\> uid of the <b><em>EmField</em></b>
  50. # <li>\<display_name\> field <b><em>display_name</em></b> of the <b><em>EmField</em></b>, in different languages, as above
  51. # <li>\<help_text\> Short explanation of the class's purpose, in different languages, as above
  52. # <li>\<requires\> all uids of the <b><em>EmGroups</em></b> required by this group and which are in the fields <b><em>require</em></b>
  53. # <li>\<components\> Set of all components of the <b><em>EmGroups</em></b>, representation of the field <b><em>__components</em></b> \n
  54. # this item is splitted in two parts :\
  55. # <ul><li>\<emfields\> all the emfields with, for each of them:\n
  56. # \<emfield\> \n
  57. # <ul><li> \<uid\> <b><em>uid</em></b> of the <b><em>EmField</em></b></ul>
  58. # <li>\<emclasses\> all the emclasses with, for each of them:\n
  59. # \<emclass\> \n
  60. # <ul><li> \<uid\> <b><em>uid</em></b> of the <b><em>EmClass</em></b></ul></ul></ul>
  61. # </ul>
  62. ##@brief Saves a model in a xml file
  63. # @param model EditorialModel : the model to save
  64. # @param kwargs dict :
  65. # - filename str|None : if None display on stdout else writes in the file filename
  66. def save(model, **kwargs):
  67. Em = etree.Element("editorial_model")
  68. em_name = etree.SubElement(Em, 'name')
  69. write_mlstring_xml(etree, em_name, model.name)
  70. em_description = etree.SubElement(Em, 'description')
  71. write_mlstring_xml(etree, em_description, model.description)
  72. em_classes = etree.SubElement(Em, 'classes')
  73. classes = model.all_classes()
  74. for emclass in classes:
  75. write_emclass_xml(etree, em_classes, classes[emclass].uid, classes[emclass].display_name,
  76. classes[emclass].help_text, classes[emclass].group,
  77. classes[emclass].fields(no_parents=True), classes[emclass].parents,
  78. classes[emclass].abstract, classes[emclass].pure_abstract)
  79. em_groups = etree.SubElement(Em, 'groups')
  80. groups = model.all_groups()
  81. for group in groups:
  82. requires = groups[group].dependencies()
  83. write_emgroup_xml(etree, em_groups, groups[group].uid, groups[group].display_name, groups[group].help_text,
  84. list(requires.keys()), groups[group].components())
  85. emodel = etree.tostring(Em, encoding='utf-8', xml_declaration=True, method='xml', pretty_print= True)
  86. if len(kwargs) == 0:
  87. print(emodel.decode())
  88. else:
  89. outfile = open(kwargs['filename'], "w")
  90. outfile.write(emodel.decode())
  91. outfile.close()
  92. ##@brief Writes a representation of a MlString in xml
  93. # @param etree : the xml object
  94. # @param elem : the element which represents a MlString
  95. # @param mlstr : the mlstr to write
  96. def write_mlstring_xml(etree, elem, mlstr):
  97. for lang in mlstr.values:
  98. ss_mlstr = etree.SubElement(elem,lang)
  99. ss_mlstr.text = mlstr.get(lang)
  100. ##@brief Writes the definition of a datahandler in xml
  101. # @param etree : the xml object
  102. # @param elem : the element which defines a datahandler
  103. # @param dhdl_name : the name of the datahandler
  104. # @param kwargs : the options of the datahandler
  105. def write_datahandler_xml(etree, elem, dhdl_name, **kwargs):
  106. dhdl = etree.SubElement(elem,'datahandler_name')
  107. dhdl.text = dhdl_name
  108. dhdl_opt = etree.SubElement(elem, 'datahandler_options')
  109. for argname, argval in kwargs.items():
  110. arg = etree.SubElement(dhdl_opt, argname)
  111. opt_val=''
  112. if (isinstance(argval, str)):
  113. opt_val=argval
  114. elif (isinstance(argval, bool)):
  115. opt_val = str(argval)
  116. elif (isinstance(argval, list) | isinstance(argval, tuple) | isinstance(argval, dict)):
  117. for argu in argval:
  118. if len(opt_val) > 0:
  119. opt_val = opt_val + ','
  120. if isinstance(argu, EmComponent):
  121. opt_val = opt_val + argu.uid
  122. elif isinstance(argu, str):
  123. opt_val = opt_val + argu
  124. else:
  125. opt_val = str(argu)
  126. arg.text = opt_val
  127. ##@brief Writes a representation in xml of a EmField
  128. # @param etree : the xml object
  129. # @param elem : the element for the EmField
  130. # @param uid : the uid of the EmField
  131. # @param name : the name of the field
  132. # @param help_text : explanations of the EmField
  133. # @param group : the uid of a group, can be None
  134. # @param datahandler_name str
  135. # @param **kwargs : options of the datahandler
  136. def write_emfield_xml(etree, elem, uid, name, help_text, group, datahandler_name, **kwargs):
  137. emfield = etree.SubElement(elem,'field')
  138. emfield_uid = etree.SubElement(emfield, 'uid')
  139. emfield_uid.text = uid
  140. emfield_name = etree.SubElement(emfield, 'display_name')
  141. if name is None:
  142. pass
  143. else:
  144. write_mlstring_xml(etree, emfield_name, name)
  145. emfield_help = etree.SubElement(emfield, 'help_text')
  146. if help_text is None:
  147. pass
  148. else:
  149. write_mlstring_xml(etree, emfield_help, help_text)
  150. emfield_group = etree.SubElement(emfield, 'group')
  151. if group is not None:
  152. emfield_group.text = group.uid #write_emgroup_xml(etree, emfield_group, group.uid, group.display_name, group.help_text, group.requires)
  153. write_datahandler_xml(etree,emfield,datahandler_name, **kwargs)
  154. ##@brief Writes a representation of a EmGroup in xml
  155. # @param etree : the xml object
  156. # @param elem : the element for the EmGroup
  157. # @param uid str : lodel unique identifier
  158. # @param name : the name of the group
  159. # @param help_text : explanations of the EmGroup
  160. # @param requires : a list of the group's uids whose this group depends
  161. # @param components
  162. def write_emgroup_xml(etree, elem, uid, name, help_text, requires, components):
  163. emgroup = etree.SubElement(elem, 'group')
  164. emgroup_uid = etree.SubElement(emgroup, 'uid')
  165. emgroup_uid.text = uid
  166. emgroup_name = etree.SubElement(emgroup, 'display_name')
  167. if name is None:
  168. pass
  169. else:
  170. write_mlstring_xml(etree, emgroup_name, name)
  171. emgroup_help = etree.SubElement(emgroup, 'help_text')
  172. if help_text is None:
  173. pass
  174. else:
  175. write_mlstring_xml(etree, emgroup_help, help_text)
  176. emgroup_requires = etree.SubElement(emgroup, 'requires')
  177. emgroup_requires.text = ",".join(requires)
  178. emgroup_comp = etree.SubElement(emgroup, 'components')
  179. emgroup_comp_cls = etree.SubElement(emgroup_comp, 'emclasses')
  180. emgroup_comp_fld = etree.SubElement(emgroup_comp, 'emfields')
  181. for component in components:
  182. if isinstance(component, EmField):
  183. emgroup_comp_fld_ins = etree.SubElement(emgroup_comp_fld, 'emfield')
  184. em_group_comp_fld_ins_uid = etree.SubElement(emgroup_comp_fld_ins,'uid')
  185. em_group_comp_fld_ins_uid.text = component.uid
  186. em_group_comp_fld_ins_cls = etree.SubElement(emgroup_comp_fld_ins,'class')
  187. em_group_comp_fld_ins_cls.text = component.get_emclass_uid()
  188. elif isinstance(component, EmClass):
  189. em_group_comp_cls_ins = etree.SubElement(emgroup_comp_cls, 'emclass')
  190. em_group_comp_cls_ins.text = component.uid
  191. ##@brief Writes a representation of a EmClass in xml
  192. # @param etree : the xml object
  193. # @param elem : the element for the EmClass
  194. # @param uid
  195. # @param name : the name of the group
  196. # @param help_text : explanations of the EmClass
  197. # @param group
  198. # @param fields : a dict
  199. # @param parents : a list of EmClass uids
  200. # @param abstract : a boolean
  201. # @param pure_abstract : a boolean
  202. def write_emclass_xml(etree, elem, uid, name, help_text, group, fields, parents, abstract = False, pure_abstract = False):
  203. emclass = etree.SubElement(elem, 'class')
  204. emclass_uid = etree.SubElement(emclass, 'uid')
  205. emclass_uid.text = uid
  206. emclass_name = etree.SubElement(emclass, 'display_name')
  207. if name is None:
  208. pass
  209. else:
  210. write_mlstring_xml(etree, emclass_name, name)
  211. emclass_help = etree.SubElement(emclass, 'help_text')
  212. if help_text is None:
  213. pass
  214. else:
  215. write_mlstring_xml(etree, emclass_help, help_text)
  216. emclass_abstract = etree.SubElement(emclass, 'abstract')
  217. emclass_abstract.text ="True" if abstract else "False"
  218. emclass_pure_abstract = etree.SubElement(emclass, 'pure_abstract')
  219. emclass_pure_abstract.text = "True" if pure_abstract else "False"
  220. emclass_group = etree.SubElement(emclass, 'group')
  221. if group is not None:
  222. emclass_group.text = group.uid
  223. emclass_fields = etree.SubElement(emclass, 'fields')
  224. for field in fields:
  225. write_emfield_xml(etree, emclass_fields, field.uid, field.display_name, field.help_text,
  226. field.group,field.data_handler_name, **field.data_handler_options)
  227. parents_list=list()
  228. for parent in parents:
  229. parents_list.append(parent.uid)
  230. emclass_parents = etree.SubElement(emclass, 'parents')
  231. emclass_parents.text = ",".join(parents_list)
  232. ##@brief Loads a model from a xml file
  233. # @param filename str : file path of the XML file from which the model will be loaded
  234. # @return a new EditorialModel object
  235. def load(filename):
  236. Em = etree.parse(filename)
  237. emodel = Em.getroot()
  238. name = emodel.find('name')
  239. description = emodel.find('description')
  240. model = EditorialModel(load_mlstring_xml(name), load_mlstring_xml(description))
  241. classes = emodel.find('classes')
  242. for emclass in classes:
  243. em_class = load_class_xml(model, emclass)
  244. if em_class.uid not in model.all_classes():
  245. model.add_class(em_class)
  246. groups = emodel.find('groups')
  247. i = 0
  248. for group in groups:
  249. grp = load_group_xml(model, group)
  250. if grp.uid not in model.all_groups():
  251. grp = model.add_group(grp)
  252. return model
  253. ##@brief Creates a EmClass from a xml description
  254. # @param elem : the element which represents the EmClass
  255. # @param model : the model which will contain the new class
  256. # @return a new EmClass object
  257. def load_class_xml(model, elem):
  258. uid = elem.find('uid').text
  259. if elem.find('display_name').text is None:
  260. name = None
  261. else:
  262. name = load_mlstring_xml(elem.find('display_name'))
  263. if elem.find('help_text').text is None:
  264. help_text = None
  265. else:
  266. help_text = load_mlstring_xml(elem.find('help_text'))
  267. abstract = (elem.find('abstract').text == 'True')
  268. pure_abstract = (elem.find('pure_abstract').text == 'True')
  269. requires = list()
  270. classes = model.all_classes()
  271. req = elem.find('parents')
  272. if req.text is not None:
  273. l_req = req.text.split(',')
  274. for r in l_req:
  275. if r in classes:
  276. requires.append(model.all_classes_ref(r))
  277. else:
  278. requires.append(model.add_class(EmClass(r)))
  279. group = elem.find('group')
  280. if group.text is not None:
  281. if group.text in model.all_groups():
  282. grp = model.all_groups_ref(group.text)
  283. else:
  284. grp = model.add_group(EmGroup(group.text))
  285. else:
  286. grp = None
  287. if uid in classes:
  288. emclass = model.all_classes_ref(uid)
  289. emclass.display_name = name
  290. emclass.help_text = help_text
  291. emclass.parents=requires
  292. emclass.group = grp
  293. emclass.abstract = abstract
  294. emclass.pure_abstract = pure_abstract
  295. else:
  296. emclass = EmClass(uid, name, help_text, abstract,requires, grp, pure_abstract)
  297. model.add_class(emclass)
  298. fields = elem.find('fields')
  299. for field in fields:
  300. emfield = load_field_xml(model, field, emclass)
  301. l_emfields = emclass.fields()
  302. incls = False
  303. for emf in l_emfields:
  304. if emfield.uid == emf.uid:
  305. incls = True
  306. break
  307. if not incls:
  308. emclass.add_field(emfield)
  309. return emclass
  310. ##@brief Creates a EmField from a xml description
  311. #@param elem : the element which represents the EmField
  312. #@param model : the model which will contain the new field
  313. #@param emclass EmClass : the EmClass of the field
  314. #@return a new EmField object
  315. def load_field_xml(model, elem, emclass):
  316. uid = elem.find('uid').text
  317. if elem.find('display_name').text is None:
  318. name = None
  319. else:
  320. name = load_mlstring_xml(elem.find('display_name'))
  321. if elem.find('help_text').text is None:
  322. help_text = None
  323. else:
  324. help_text = load_mlstring_xml(elem.find('help_text'))
  325. emgroup = elem.find('group')
  326. if emgroup.text is not None:
  327. if emgroup.text in model.all_groups():
  328. group = model.all_groups_ref(emgroup.text)
  329. else:
  330. group = model.add_group(EmGroup(emgroup.text))
  331. else:
  332. group = None
  333. dhdl = elem.find('datahandler_name')
  334. dhdl_opts = {}
  335. if dhdl.text is not None:
  336. dhdl_opts = elem.find('datahandler_options')
  337. if dhdl_opts is not None:
  338. dhdl_options = load_dhdl_options_xml(model, dhdl_opts)
  339. emfield = EmField(
  340. uid, dhdl.text, emclass, name, help_text, group, **dhdl_options)
  341. return emfield
  342. ##@brief Returns datahandler options from a xml description
  343. # @param elem : the element which represents the datahandler
  344. # @param model : the model which will contain the new field
  345. # @return datahandler options
  346. def load_dhdl_options_xml(model, elem):
  347. dhdl_options=dict()
  348. for opt in elem:
  349. if (opt.tag == 'allowed_classes'):
  350. classes = list()
  351. if opt.text is not None:
  352. clss = opt.text.split(',')
  353. for classe in clss:
  354. if classe in model.all_classes():
  355. classes.append(model.all_classes_ref(classe))
  356. else:
  357. new_cls = model.add_class(EmClass(classe))
  358. classes.append(new_cls)
  359. dhdl_options['allowed_classes'] = classes
  360. elif (opt.tag == 'back_reference'):
  361. dhdl_options['back_reference'] = tuple(opt.text.split(','))
  362. elif ((opt.text == 'True') | (opt.text == 'False')):
  363. dhdl_options[opt.tag] = (opt.text == 'True')
  364. else:
  365. dhdl_options[opt.tag] = opt.text
  366. return dhdl_options
  367. ##@brief Creates a EmGroup from a xml description
  368. # @param elem : the element which represents the EmGroup
  369. # @param model : the model which will contain the new group
  370. # @return a new EmGroup object
  371. def load_group_xml(model, elem):
  372. uid = elem.find('uid')
  373. if elem.find('display_name').text is None:
  374. name = None
  375. else:
  376. name = load_mlstring_xml(elem.find('display_name'))
  377. if elem.find('help_text').text is None:
  378. help_text = None
  379. else:
  380. help_text = load_mlstring_xml(elem.find('help_text'))
  381. requires = list()
  382. groups = model.all_groups()
  383. req = elem.find('requires')
  384. if req.text is not None:
  385. l_req = req.text.split(',')
  386. for r in l_req:
  387. if r in groups:
  388. requires.append(model.all_groups_ref(r))
  389. else:
  390. grp = model.new_group(r)
  391. requires.append(grp)
  392. comp= list()
  393. components = elem.find('components')
  394. fields = components.find('emfields')
  395. for field in fields:
  396. fld_uid = field.find('uid').text
  397. fld_class = field.find('class').text
  398. fld = model.all_classes_ref(fld_class).fields(fld_uid)
  399. comp.append(fld)
  400. classes = components.find('emclasses')
  401. for classe in classes:
  402. comp.append(model.all_classes_ref(classe.text))
  403. groups = model.all_groups()
  404. if uid.text in groups:
  405. group = model.all_groups_ref(uid.text)
  406. group.display_name = name
  407. group.help_text = help_text
  408. group.add_dependencie(requires)
  409. else:
  410. group = EmGroup(uid.text, requires, name, help_text)
  411. group.add_components(comp)
  412. return group
  413. ##@brief Constructs a MlString from a xml description
  414. # @param elem : the element which represents the MlString
  415. # @return a new MlString object
  416. def load_mlstring_xml(elem):
  417. mlstr = dict()
  418. for lang in elem:
  419. mlstr[lang.tag] = lang.text
  420. return MlString(mlstr)