[320] | 1 | import logging |
---|
| 2 | import os.path |
---|
| 3 | from xml.dom import minidom |
---|
| 4 | from DateTime import DateTime |
---|
| 5 | |
---|
| 6 | from zope.interface import implements, Interface |
---|
| 7 | from zope.component import getUtility |
---|
| 8 | |
---|
| 9 | from ZODB.POSException import ConflictError |
---|
[323] | 10 | from Acquisition import aq_inner, aq_parent |
---|
[320] | 11 | |
---|
| 12 | from Products.Marshall.registry import getComponent |
---|
| 13 | from Products.Marshall.config import AT_NS |
---|
| 14 | from Products.CMFPlone.utils import _createObjectByType |
---|
| 15 | |
---|
| 16 | from quintagroup.transmogrifier.interfaces import IImportDataCorrector |
---|
| 17 | from quintagroup.transmogrifier.adapters.importing import ReferenceImporter |
---|
| 18 | from quintagroup.transmogrifier.xslt import XSLTSection |
---|
| 19 | |
---|
| 20 | BOOL_FIELD_PROPS = ['enabled', 'required', 'hidden'] |
---|
| 21 | |
---|
| 22 | FIELD_MAP = { |
---|
| 23 | 'StringField' : 'FormStringField', |
---|
| 24 | 'EmailField' : ('FormStringField', {'fgStringValidator': 'isEmail'}), |
---|
| 25 | 'LinkField' : ('FormStringField', {'fgStringValidator': 'isURL'}), |
---|
| 26 | 'PatternField' : 'FormStringField', |
---|
| 27 | |
---|
| 28 | 'TextAreaField' : 'FormTextField', |
---|
| 29 | 'RawTextAreaField': 'FormTextField', |
---|
| 30 | |
---|
| 31 | 'PasswordField' : 'FormPasswordField', |
---|
| 32 | 'LabelField' : 'FormRichLabelField', #FormLabelField |
---|
| 33 | |
---|
| 34 | 'IntegerField' : 'FormIntegerField', |
---|
| 35 | 'FloatField' : 'FormFixedPointField', |
---|
| 36 | |
---|
| 37 | 'DateTimeField' : 'FormDateField', |
---|
| 38 | 'FileField' : 'FormFileField', |
---|
| 39 | |
---|
| 40 | 'LinesField' : 'FormLinesField', |
---|
| 41 | |
---|
| 42 | 'CheckBoxField' : 'FormBooleanField', |
---|
| 43 | 'ListField' : ('FormSelectionField', {'fgFormat': 'select'}), |
---|
| 44 | 'RadioField' : ('FormSelectionField', {'fgFormat': 'radio'}), |
---|
| 45 | 'MultiListField': ('FormMultiSelectionField', {'fgFormat': 'select'}), |
---|
| 46 | 'MultiCheckBoxField':('FormMultiSelectionField', {'fgFormat': 'checkbox'}), |
---|
| 47 | } |
---|
| 48 | |
---|
| 49 | class IXMLDemarshaller(Interface): |
---|
| 50 | """ |
---|
| 51 | """ |
---|
| 52 | def demarshall(obj, data): |
---|
| 53 | """ |
---|
| 54 | """ |
---|
| 55 | |
---|
| 56 | class FormFolderImporter(ReferenceImporter): |
---|
| 57 | """ Demarshaller of PloneFormGen's FormFolder content type. |
---|
| 58 | """ |
---|
| 59 | implements(IImportDataCorrector) |
---|
| 60 | |
---|
[1496] | 61 | def __init__(self, context, transmogrifier): |
---|
[320] | 62 | self.context = context |
---|
| 63 | self.demarshaller = getComponent("atxml") |
---|
[329] | 64 | self.auto_added_fgfields = ['replyto', 'topic', 'comments'] |
---|
| 65 | self.date_fields = {} |
---|
[320] | 66 | |
---|
| 67 | def __call__(self, data): |
---|
| 68 | data = super(FormFolderImporter, self).__call__(data) |
---|
| 69 | xml = data['data'] |
---|
[329] | 70 | data['data'] = self.transformWithMinidom(xml) |
---|
[320] | 71 | |
---|
| 72 | # update FormMailerAdapter and FormThanksPage objects |
---|
| 73 | cleaned_xml = self.updateMailer(xml) |
---|
[323] | 74 | |
---|
[320] | 75 | self.updateResponsePage(cleaned_xml) |
---|
| 76 | |
---|
| 77 | self.updateFormFields(xml) |
---|
| 78 | |
---|
| 79 | return data |
---|
| 80 | |
---|
[329] | 81 | def transformWithMinidom(self, xml): |
---|
[320] | 82 | """ Do some extra transformations on xml document (that can be done by XSLT). |
---|
| 83 | """ |
---|
| 84 | doc = minidom.parseString(xml) |
---|
| 85 | root = doc.documentElement |
---|
| 86 | |
---|
[329] | 87 | # get 'modification_date' and 'creation_date' for next usage when creating fields |
---|
| 88 | for i in root.getElementsByTagName('xmp:CreateDate'): |
---|
| 89 | self.date_fields['creation_date'] = i.firstChild.nodeValue.strip() |
---|
| 90 | for i in root.getElementsByTagName('xmp:ModifyDate'): |
---|
| 91 | self.date_fields['modification_date'] = i.firstChild.nodeValue.strip() |
---|
| 92 | |
---|
[320] | 93 | # xxx: update button labels (those elements are now skiped by xslt) |
---|
| 94 | # PFM has only one field 'form_buttons', but PFG has two: 'submitLabel', 'resetLabel' and |
---|
| 95 | # field 'useCancelButton' |
---|
| 96 | |
---|
| 97 | # add element for setting of 'thanksPage' field to 'thank-you' FormThanksPage |
---|
| 98 | elem = doc.createElementNS(AT_NS, "field") |
---|
| 99 | name_attr = doc.createAttribute("name") |
---|
| 100 | name_attr.value = 'thanksPage' |
---|
| 101 | elem.setAttributeNode(name_attr) |
---|
| 102 | value = doc.createTextNode('thank-you') |
---|
| 103 | elem.appendChild(value) |
---|
| 104 | root.appendChild(elem) |
---|
| 105 | |
---|
| 106 | # update 'thanksPageOverride' field |
---|
| 107 | elem = [i for i in doc.getElementsByTagName('field') if i.getAttribute('name') == 'thanksPageOverride'] |
---|
| 108 | if elem: |
---|
| 109 | elem = elem[0] |
---|
| 110 | old_text = elem.firstChild |
---|
| 111 | new_text = doc.createTextNode('redirect_to:' + old_text.nodeValue.strip()) |
---|
| 112 | elem.removeChild(old_text) |
---|
| 113 | elem.appendChild(new_text) |
---|
| 114 | |
---|
| 115 | # xxx: update 'afterValidationOverride' field |
---|
| 116 | # 'afterValidationOverride' is TALES expression, but PFM's 'cpyaction' is name |
---|
| 117 | # of controller python script |
---|
| 118 | elem = [i for i in doc.getElementsByTagName('field') |
---|
| 119 | if i.getAttribute('name') == 'afterValidationOverride'] |
---|
| 120 | if elem: |
---|
| 121 | elem = elem[0] |
---|
| 122 | old_text = elem.firstChild |
---|
| 123 | new_text = doc.createTextNode('string:' + old_text.nodeValue.strip()) |
---|
| 124 | elem.removeChild(old_text) |
---|
| 125 | elem.appendChild(new_text) |
---|
| 126 | |
---|
| 127 | # xxx: update 'onDisplayOverride' field (this element is now skiped by xslt) |
---|
| 128 | # 'onDisplayOverride' is TALES expression, but PFM's 'before_script' is python script |
---|
| 129 | # in 'scripts' subfolder |
---|
| 130 | |
---|
| 131 | return doc.toxml('utf-8') |
---|
| 132 | |
---|
| 133 | def updateMailer(self, xml): |
---|
| 134 | mailer = self.context['mailer'] |
---|
[329] | 135 | transformed = self.transformWithXSLT(xml, 'FormMailerAdapter') |
---|
[320] | 136 | self.demarshaller.demarshall(mailer, transformed) |
---|
| 137 | mailer.indexObject() |
---|
| 138 | return transformed |
---|
| 139 | |
---|
| 140 | def updateResponsePage(self, xml): |
---|
| 141 | page = self.context['thank-you'] |
---|
[329] | 142 | transformed = self.transformWithXSLT(xml, 'FormThanksPage') |
---|
[320] | 143 | self.demarshaller.demarshall(page, transformed) |
---|
[323] | 144 | # override default value of description field |
---|
| 145 | page.getField('description').getMutator(page)('') |
---|
[320] | 146 | page.indexObject() |
---|
| 147 | return transformed |
---|
| 148 | |
---|
[329] | 149 | def transformWithXSLT(self, xml, to): |
---|
[320] | 150 | """ Apply XSLT transformations using XSLT transmogrifier section. |
---|
| 151 | """ |
---|
| 152 | item = dict( |
---|
| 153 | _from='PloneFormMailer', |
---|
| 154 | _to=to, |
---|
| 155 | _files=dict( |
---|
[323] | 156 | marshall=dict( |
---|
| 157 | name='.marshall.xml', |
---|
| 158 | data=xml |
---|
| 159 | ) |
---|
[320] | 160 | ) |
---|
| 161 | ) |
---|
| 162 | section = XSLTSection(object(), 'xslt', {'blueprint': ''}, iter((item,))) |
---|
| 163 | for i in section: pass |
---|
| 164 | return item['_files']['marshall']['data'] |
---|
| 165 | |
---|
| 166 | def updateFormFields(self, data): |
---|
| 167 | """ Walk trough xml tree and create fields in FormFolder. |
---|
| 168 | """ |
---|
| 169 | # delete fields that were added on FormFolder creation |
---|
[329] | 170 | for oid in [i for i in self.auto_added_fgfields if i in self.context]: |
---|
[320] | 171 | self.context._delObject(oid) |
---|
| 172 | |
---|
| 173 | doc = minidom.parseString(data) |
---|
| 174 | root = doc.documentElement |
---|
| 175 | form_element = root.getElementsByTagName('form') |
---|
| 176 | if not form_element: |
---|
| 177 | return |
---|
| 178 | groups = form_element[0].getElementsByTagName('group') |
---|
| 179 | for group in groups: |
---|
| 180 | group_title = group.getElementsByTagName('title')[0] |
---|
| 181 | group_title = str(group_title.firstChild.nodeValue).strip() |
---|
| 182 | if group_title == 'Default': |
---|
| 183 | for field in group.getElementsByTagName('field'): |
---|
| 184 | field_id = str(field.getElementsByTagName('id')[0].firstChild.nodeValue).strip() |
---|
| 185 | field_type = str(field.getElementsByTagName('type')[0].firstChild.nodeValue).strip() |
---|
| 186 | self.createField(field_type, field_id, field, self.context) |
---|
| 187 | else: |
---|
| 188 | fieldset = self.createFieldset(group_title) |
---|
| 189 | if not fieldset: |
---|
| 190 | return |
---|
| 191 | for field in group.getElementsByTagName('field'): |
---|
| 192 | field_id = str(field.getElementsByTagName('id')[0].firstChild.nodeValue).strip() |
---|
| 193 | field_type = str(field.getElementsByTagName('type')[0].firstChild.nodeValue).strip() |
---|
| 194 | self.createField(field_type, field_id, field, fieldset) |
---|
| 195 | |
---|
| 196 | def createField(self, type_name, field_id, field_node, context=None): |
---|
| 197 | """ Created formfield and update it from field_node's xml tree. |
---|
| 198 | |
---|
| 199 | Use demarshalling adapter. |
---|
| 200 | """ |
---|
| 201 | if FIELD_MAP.get(type_name) is None: |
---|
| 202 | return |
---|
| 203 | |
---|
| 204 | if isinstance(FIELD_MAP[type_name], tuple): |
---|
| 205 | type_name, options = FIELD_MAP[type_name] |
---|
| 206 | else: |
---|
| 207 | type_name, options = FIELD_MAP[type_name], {} |
---|
[329] | 208 | # update options with 'creation_date' and 'modification_date', that are |
---|
| 209 | # the same as in FormFolder (dates on fields must be the same) |
---|
| 210 | options.update(self.date_fields) |
---|
[320] | 211 | |
---|
| 212 | if field_id not in context.contentIds(): |
---|
| 213 | try: |
---|
| 214 | field = _createObjectByType(type_name, context, field_id) |
---|
| 215 | except ConflictError: |
---|
| 216 | raise |
---|
| 217 | except: |
---|
| 218 | return |
---|
| 219 | else: |
---|
| 220 | field = context._getOb(field_id) |
---|
| 221 | |
---|
| 222 | try: |
---|
| 223 | IXMLDemarshaller(field).demarshall(field_node, **options) |
---|
| 224 | except ConflictError: |
---|
| 225 | raise |
---|
| 226 | |
---|
| 227 | def createFieldset(self, title): |
---|
| 228 | """ Create FieldsetFolder with id=title |
---|
| 229 | """ |
---|
| 230 | if title not in self.context.contentIds(): |
---|
| 231 | try: |
---|
| 232 | fieldset = _createObjectByType('FieldsetFolder', self.context, title) |
---|
| 233 | except ConflictError: |
---|
| 234 | raise |
---|
| 235 | except: |
---|
| 236 | return |
---|
| 237 | else: |
---|
| 238 | fieldset = self.context._getOb(title) |
---|
| 239 | fieldset.Schema()['title'].getMutator(fieldset)(title) |
---|
| 240 | |
---|
| 241 | return fieldset |
---|
| 242 | |
---|
| 243 | class BaseFieldDemarshaller(object): |
---|
| 244 | """ Base class for demarshallers of PloneFormGen's fields from PloneFormMailer fields. |
---|
| 245 | """ |
---|
| 246 | implements(IXMLDemarshaller) |
---|
| 247 | |
---|
| 248 | def __init__(self, context): |
---|
| 249 | self.context = context |
---|
| 250 | |
---|
| 251 | def demarshall(self, node, **kw): |
---|
| 252 | self.extractData(node) |
---|
[329] | 253 | # update data dictionary form keyword arguments |
---|
[320] | 254 | for k, v in kw.items(): |
---|
| 255 | self.data[k] = v |
---|
| 256 | # call special hook for changing data |
---|
| 257 | self.modifyData() |
---|
| 258 | |
---|
| 259 | # update instance |
---|
| 260 | schema = self.context.Schema() |
---|
| 261 | for fname, value in self.data.items(): |
---|
| 262 | if not schema.has_key(fname): |
---|
| 263 | continue |
---|
| 264 | mutator = schema[fname].getMutator(self.context) |
---|
| 265 | if not mutator: |
---|
| 266 | continue |
---|
| 267 | mutator(value) |
---|
| 268 | |
---|
[323] | 269 | self.context.indexObject() |
---|
| 270 | |
---|
[320] | 271 | def extractData(self, node): |
---|
| 272 | tree = XMLObject() |
---|
| 273 | elementToObject(tree, node) |
---|
| 274 | simplify_single_entries(tree) |
---|
| 275 | |
---|
| 276 | data = {} |
---|
| 277 | values = tree.first.field.first.values |
---|
| 278 | for name in values.getElementNames(): |
---|
| 279 | value = getattr(values.first, name) |
---|
| 280 | if value.attributes.get('type') == 'float': |
---|
| 281 | data[name] = float(value.text) |
---|
| 282 | elif value.attributes.get('type') == 'int': |
---|
| 283 | data[name] = int(value.text) |
---|
| 284 | # boolean property is exported as int, fix that |
---|
| 285 | if name in BOOL_FIELD_PROPS: |
---|
| 286 | data[name] = bool(data[name]) |
---|
| 287 | elif value.attributes.get('type') == 'list': |
---|
| 288 | # XXX bare eval here (this may be a security leak ?) |
---|
| 289 | data[name] = eval(str(value.text)) |
---|
| 290 | elif value.attributes.get('type') == 'datetime': |
---|
| 291 | data[name] = DateTime(value.text) |
---|
| 292 | else: |
---|
| 293 | data[name] = str(value.text) |
---|
| 294 | |
---|
[329] | 295 | # get tales expressions |
---|
| 296 | tales = tree.first.field.first.tales |
---|
| 297 | for name in tales.getElementNames(): |
---|
| 298 | value = getattr(tales.first, name) |
---|
| 299 | data["tales:"+name] = str(value.text) |
---|
| 300 | |
---|
[320] | 301 | self.data = data |
---|
| 302 | |
---|
| 303 | def modifyData(self): |
---|
| 304 | pass |
---|
| 305 | |
---|
| 306 | def renameEntry(self, old, new): |
---|
| 307 | if self.data.has_key(old): |
---|
| 308 | self.data[new] = self.data.pop(old) |
---|
| 309 | |
---|
[329] | 310 | def entryIsDigit(self, key): |
---|
| 311 | """ Formulator exports empty fields in xml as empty elements without |
---|
| 312 | 'type' attribute. That's is why we get in data dictionary for |
---|
| 313 | integer fields values that are empty strings. This method is |
---|
| 314 | used to check whether integer field has integer value. |
---|
| 315 | """ |
---|
| 316 | if key in self.data and isinstance(self.data[key], int): |
---|
| 317 | return True |
---|
| 318 | else: |
---|
| 319 | return False |
---|
| 320 | |
---|
[320] | 321 | class StringFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 322 | """ Demarshaller of StringField and other fields of this kind. |
---|
| 323 | """ |
---|
| 324 | |
---|
| 325 | def modifyData(self): |
---|
| 326 | self.renameEntry('default', 'fgDefault') |
---|
[329] | 327 | if self.entryIsDigit('display_maxwidth'): |
---|
| 328 | self.renameEntry('display_maxwidth', 'fgmaxlength') |
---|
| 329 | if self.entryIsDigit('display_width'): |
---|
| 330 | self.renameEntry('display_width', 'fgsize') |
---|
[320] | 331 | |
---|
| 332 | class TextFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 333 | """ Demarshaller of TextField. |
---|
| 334 | """ |
---|
| 335 | |
---|
| 336 | def modifyData(self): |
---|
| 337 | self.renameEntry('default', 'fgDefault') |
---|
[329] | 338 | if self.entryIsDigit('max_length'): |
---|
| 339 | self.renameEntry('max_length', 'fgmaxlength') |
---|
| 340 | if self.entryIsDigit('height'): |
---|
| 341 | self.renameEntry('height', 'fgRows') |
---|
[320] | 342 | |
---|
| 343 | class LabelFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 344 | """ Demarshaller of LabelField. |
---|
| 345 | """ |
---|
| 346 | |
---|
| 347 | def modifyData(self): |
---|
| 348 | self.renameEntry('default', 'fgDefault') |
---|
| 349 | |
---|
[329] | 350 | class IntegerFieldDemarshaller(BaseFieldDemarshaller): |
---|
[320] | 351 | """ Demarshaller of IntegerField. |
---|
| 352 | """ |
---|
| 353 | |
---|
| 354 | def modifyData(self): |
---|
[329] | 355 | self.renameEntry('default', 'fgDefault') |
---|
| 356 | if self.entryIsDigit('display_maxwidth'): |
---|
| 357 | self.renameEntry('display_maxwidth', 'fgmaxlength') |
---|
| 358 | if self.entryIsDigit('display_width'): |
---|
| 359 | self.renameEntry('display_width', 'fgsize') |
---|
| 360 | if self.entryIsDigit('start'): |
---|
| 361 | self.renameEntry('start', 'minval') |
---|
| 362 | if self.entryIsDigit('end'): |
---|
| 363 | self.renameEntry('end', 'maxval') |
---|
[320] | 364 | |
---|
| 365 | class DateTimeFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 366 | """ Demarshaller of DateTimeField. |
---|
| 367 | """ |
---|
| 368 | |
---|
| 369 | def modifyData(self): |
---|
| 370 | if 'default' in self.data: |
---|
[330] | 371 | # convert from DateTime object to string |
---|
[320] | 372 | self.data['default'] = str(self.data['default']) |
---|
| 373 | self.renameEntry('default', 'fgDefault') |
---|
| 374 | # date_only is boolean flag |
---|
| 375 | self.data['fgShowHM'] = not bool(self.data['date_only']) |
---|
| 376 | if 'start_datetime' in self.data: |
---|
| 377 | self.data['start_datetime'] = self.data['start_datetime'].year() |
---|
| 378 | self.renameEntry('start_datetime', 'fgStartingYear') |
---|
| 379 | if 'end_datetime' in self.data: |
---|
| 380 | self.data['end_datetime'] = self.data['end_datetime'].year() |
---|
| 381 | self.renameEntry('end_datetime', 'fgEndingYear') |
---|
| 382 | |
---|
| 383 | class LinesFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 384 | """ Demarshaller of LinesField. |
---|
| 385 | """ |
---|
| 386 | |
---|
| 387 | def modifyData(self): |
---|
| 388 | self.renameEntry('default', 'fgDefault') |
---|
[329] | 389 | if self.entryIsDigit('height'): |
---|
| 390 | self.renameEntry('height', 'fgRows') |
---|
[320] | 391 | |
---|
| 392 | class BooleanFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 393 | """ Demarshaller of BooleanField. |
---|
| 394 | """ |
---|
| 395 | |
---|
| 396 | def modifyData(self): |
---|
| 397 | self.data['default'] = bool(self.data['default']) |
---|
| 398 | self.renameEntry('default', 'fgDefault') |
---|
| 399 | |
---|
| 400 | |
---|
| 401 | class SelectionFieldDemarshaller(BaseFieldDemarshaller): |
---|
| 402 | """ Demarshaller of SelectionField. |
---|
| 403 | """ |
---|
| 404 | |
---|
| 405 | def modifyData(self): |
---|
| 406 | self.renameEntry('default', 'fgDefault') |
---|
[329] | 407 | l = [] |
---|
| 408 | for i in self.data['items']: |
---|
| 409 | i = list(i) |
---|
| 410 | i.reverse() |
---|
| 411 | l.append(i) |
---|
| 412 | self.data['items'] = ['|'.join(i) for i in l] |
---|
[320] | 413 | self.renameEntry('items', 'fgVocabulary') |
---|
| 414 | |
---|
[329] | 415 | class MultiSelectFieldDemarshaller(SelectionFieldDemarshaller): |
---|
[320] | 416 | """ Demarshaller of MultiSelectField. |
---|
| 417 | """ |
---|
| 418 | |
---|
| 419 | def modifyData(self): |
---|
| 420 | super(MultiSelectFieldDemarshaller, self).modifyData() |
---|
[329] | 421 | if self.entryIsDigit('size'): |
---|
| 422 | self.renameEntry('size', 'fgRows') |
---|
[320] | 423 | |
---|
| 424 | |
---|
| 425 | # next code was stolen from Formulator product |
---|
| 426 | from xml.dom.minidom import parse, parseString, Node |
---|
| 427 | |
---|
| 428 | # an extremely simple system for loading in XML into objects |
---|
| 429 | |
---|
| 430 | class Object: |
---|
| 431 | pass |
---|
| 432 | |
---|
| 433 | class XMLObject: |
---|
| 434 | def __init__(self): |
---|
| 435 | self.elements = Object() |
---|
| 436 | self.first = Object() |
---|
| 437 | self.attributes = {} |
---|
| 438 | self.text = '' |
---|
| 439 | |
---|
| 440 | def getElementNames(self): |
---|
| 441 | return [element for element in dir(self.elements) |
---|
| 442 | if not element.startswith('__')] |
---|
| 443 | |
---|
| 444 | def getAttributes(self): |
---|
| 445 | return self.attributes |
---|
| 446 | |
---|
| 447 | def elementToObject(parent, node): |
---|
| 448 | # create an object to represent element node |
---|
| 449 | object = XMLObject() |
---|
| 450 | # make object attributes off node attributes |
---|
| 451 | for key, value in node.attributes.items(): |
---|
| 452 | object.attributes[key] = value |
---|
| 453 | # make lists of child elements (or ignore them) |
---|
| 454 | for child in node.childNodes: |
---|
| 455 | nodeToObject(object, child) |
---|
| 456 | # add ourselves to parent node |
---|
| 457 | name = str(node.nodeName) |
---|
| 458 | l = getattr(parent.elements, name, []) |
---|
| 459 | l.append(object) |
---|
| 460 | setattr(parent.elements, name, l) |
---|
| 461 | |
---|
| 462 | def attributeToObject(parent, node): |
---|
| 463 | # should never be called |
---|
| 464 | pass |
---|
| 465 | |
---|
| 466 | def textToObject(parent, node): |
---|
| 467 | # add this text to parents text content |
---|
| 468 | parent.text += node.data |
---|
| 469 | |
---|
| 470 | def processingInstructionToObject(parent, node): |
---|
| 471 | # don't do anything with these |
---|
| 472 | pass |
---|
| 473 | |
---|
| 474 | def commentToObject(parent, node): |
---|
| 475 | # don't do anything with these |
---|
| 476 | pass |
---|
| 477 | |
---|
| 478 | def documentToObject(parent, node): |
---|
| 479 | elementToObject(parent, node.documentElement) |
---|
| 480 | |
---|
| 481 | def documentTypeToObject(parent, node): |
---|
| 482 | # don't do anything with these |
---|
| 483 | pass |
---|
| 484 | |
---|
| 485 | _map = { |
---|
| 486 | Node.ELEMENT_NODE: elementToObject, |
---|
| 487 | Node.ATTRIBUTE_NODE: attributeToObject, |
---|
| 488 | Node.TEXT_NODE: textToObject, |
---|
| 489 | # Node.CDATA_SECTION_NODE: |
---|
| 490 | # Node.ENTITY_NODE: |
---|
| 491 | Node.PROCESSING_INSTRUCTION_NODE: processingInstructionToObject, |
---|
| 492 | Node.COMMENT_NODE: commentToObject, |
---|
| 493 | Node.DOCUMENT_NODE: documentToObject, |
---|
| 494 | Node.DOCUMENT_TYPE_NODE: documentTypeToObject, |
---|
| 495 | # Node.NOTATION_NODE: |
---|
| 496 | } |
---|
| 497 | |
---|
| 498 | def nodeToObject(parent, node): |
---|
| 499 | _map[node.nodeType](parent, node) |
---|
| 500 | |
---|
| 501 | def simplify_single_entries(object): |
---|
| 502 | for name in object.getElementNames(): |
---|
| 503 | l = getattr(object.elements, name) |
---|
| 504 | # set the first subelement (in case it's just one, this is easy) |
---|
| 505 | setattr(object.first, name, l[0]) |
---|
| 506 | # now do the same for rest |
---|
| 507 | for element in l: |
---|
| 508 | simplify_single_entries(element) |
---|
| 509 | |
---|
| 510 | def XMLToObjectsFromFile(path): |
---|
| 511 | return XMLToObjects(parse(path)) |
---|
| 512 | |
---|
| 513 | def XMLToObjectsFromString(s): |
---|
| 514 | return XMLToObjects(parseString(s)) |
---|
| 515 | |
---|
| 516 | def XMLToObjects(document): |
---|
| 517 | object = XMLObject() |
---|
| 518 | documentToObject(object, document) |
---|
| 519 | document.unlink() |
---|
| 520 | simplify_single_entries(object) |
---|
| 521 | return object |
---|
| 522 | |
---|