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 20KB

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