diff options
Diffstat (limited to 'libmat/hachoir_editor/fieldset.py')
| -rw-r--r-- | libmat/hachoir_editor/fieldset.py | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/libmat/hachoir_editor/fieldset.py b/libmat/hachoir_editor/fieldset.py new file mode 100644 index 0000000..b7c9b07 --- /dev/null +++ b/libmat/hachoir_editor/fieldset.py | |||
| @@ -0,0 +1,352 @@ | |||
| 1 | from hachoir_core.dict import UniqKeyError | ||
| 2 | from hachoir_core.field import MissingField, Float32, Float64, FakeArray | ||
| 3 | from hachoir_core.compatibility import any | ||
| 4 | from hachoir_core.i18n import _ | ||
| 5 | from typed_field import createEditableField | ||
| 6 | from field import EditorError | ||
| 7 | from collections import deque # Python 2.4 | ||
| 8 | import weakref # Python 2.1 | ||
| 9 | import struct | ||
| 10 | |||
| 11 | class EditableFieldSet(object): | ||
| 12 | MAX_SIZE = (1 << 40) # Arbitrary limit to catch errors | ||
| 13 | is_field_set = True | ||
| 14 | |||
| 15 | def __init__(self, parent, fieldset): | ||
| 16 | self._parent = parent | ||
| 17 | self.input = fieldset # original FieldSet | ||
| 18 | self._fields = {} # cache of editable fields | ||
| 19 | self._deleted = set() # Names of deleted fields | ||
| 20 | self._inserted = {} # Inserted field (name => list of field, | ||
| 21 | # where name is the name after) | ||
| 22 | |||
| 23 | def array(self, key): | ||
| 24 | # FIXME: Use cache? | ||
| 25 | return FakeArray(self, key) | ||
| 26 | |||
| 27 | def _getParent(self): | ||
| 28 | return self._parent | ||
| 29 | parent = property(_getParent) | ||
| 30 | |||
| 31 | def _isAltered(self): | ||
| 32 | if self._inserted: | ||
| 33 | return True | ||
| 34 | if self._deleted: | ||
| 35 | return True | ||
| 36 | return any(field.is_altered for field in self._fields.itervalues()) | ||
| 37 | is_altered = property(_isAltered) | ||
| 38 | |||
| 39 | def reset(self): | ||
| 40 | """ | ||
| 41 | Reset the field set and the input field set. | ||
| 42 | """ | ||
| 43 | for key, field in self._fields.iteritems(): | ||
| 44 | if not field.is_altered: | ||
| 45 | del self._fields[key] | ||
| 46 | self.input.reset() | ||
| 47 | |||
| 48 | def __len__(self): | ||
| 49 | return len(self.input) \ | ||
| 50 | - len(self._deleted) \ | ||
| 51 | + sum( len(new) for new in self._inserted.itervalues() ) | ||
| 52 | |||
| 53 | def __iter__(self): | ||
| 54 | for field in self.input: | ||
| 55 | name = field.name | ||
| 56 | if name in self._inserted: | ||
| 57 | for newfield in self._inserted[name]: | ||
| 58 | yield weakref.proxy(newfield) | ||
| 59 | if name not in self._deleted: | ||
| 60 | yield self[name] | ||
| 61 | if None in self._inserted: | ||
| 62 | for newfield in self._inserted[None]: | ||
| 63 | yield weakref.proxy(newfield) | ||
| 64 | |||
| 65 | def insertBefore(self, name, *new_fields): | ||
| 66 | self._insert(name, new_fields, False) | ||
| 67 | |||
| 68 | def insertAfter(self, name, *new_fields): | ||
| 69 | self._insert(name, new_fields, True) | ||
| 70 | |||
| 71 | def insert(self, *new_fields): | ||
| 72 | self._insert(None, new_fields, True) | ||
| 73 | |||
| 74 | def _insert(self, key, new_fields, next): | ||
| 75 | """ | ||
| 76 | key is the name of the field before which new_fields | ||
| 77 | will be inserted. If next is True, the fields will be inserted | ||
| 78 | _after_ this field. | ||
| 79 | """ | ||
| 80 | # Set unique field name | ||
| 81 | for field in new_fields: | ||
| 82 | if field._name.endswith("[]"): | ||
| 83 | self.input.setUniqueFieldName(field) | ||
| 84 | |||
| 85 | # Check that there is no duplicate in inserted fields | ||
| 86 | new_names = list(field.name for field in new_fields) | ||
| 87 | names_set = set(new_names) | ||
| 88 | if len(names_set) != len(new_fields): | ||
| 89 | duplicates = (name for name in names_set if 1 < new_names.count(name)) | ||
| 90 | raise UniqKeyError(_("Duplicates in inserted fields: %s") % ", ".join(duplicates)) | ||
| 91 | |||
| 92 | # Check that field names are not in input | ||
| 93 | if self.input: # Write special version for NewFieldSet? | ||
| 94 | for name in new_names: | ||
| 95 | if name in self.input and name not in self._deleted: | ||
| 96 | raise UniqKeyError(_("Field name '%s' already exists") % name) | ||
| 97 | |||
| 98 | # Check that field names are not in inserted fields | ||
| 99 | for fields in self._inserted.itervalues(): | ||
| 100 | for field in fields: | ||
| 101 | if field.name in new_names: | ||
| 102 | raise UniqKeyError(_("Field name '%s' already exists") % field.name) | ||
| 103 | |||
| 104 | # Input have already inserted field? | ||
| 105 | if key in self._inserted: | ||
| 106 | if next: | ||
| 107 | self._inserted[key].extend( reversed(new_fields) ) | ||
| 108 | else: | ||
| 109 | self._inserted[key].extendleft( reversed(new_fields) ) | ||
| 110 | return | ||
| 111 | |||
| 112 | # Whould like to insert in inserted fields? | ||
| 113 | if key: | ||
| 114 | for fields in self._inserted.itervalues(): | ||
| 115 | names = [item.name for item in fields] | ||
| 116 | try: | ||
| 117 | pos = names.index(key) | ||
| 118 | except ValueError: | ||
| 119 | continue | ||
| 120 | if 0 <= pos: | ||
| 121 | if next: | ||
| 122 | pos += 1 | ||
| 123 | fields.rotate(-pos) | ||
| 124 | fields.extendleft( reversed(new_fields) ) | ||
| 125 | fields.rotate(pos) | ||
| 126 | return | ||
| 127 | |||
| 128 | # Get next field. Use None if we are at the end. | ||
| 129 | if next: | ||
| 130 | index = self.input[key].index + 1 | ||
| 131 | try: | ||
| 132 | key = self.input[index].name | ||
| 133 | except IndexError: | ||
| 134 | key = None | ||
| 135 | |||
| 136 | # Check that field names are not in input | ||
| 137 | if key not in self.input: | ||
| 138 | raise MissingField(self, key) | ||
| 139 | |||
| 140 | # Insert in original input | ||
| 141 | self._inserted[key]= deque(new_fields) | ||
| 142 | |||
| 143 | def _getDescription(self): | ||
| 144 | return self.input.description | ||
| 145 | description = property(_getDescription) | ||
| 146 | |||
| 147 | def _getStream(self): | ||
| 148 | # FIXME: This property is maybe a bad idea since address may be differents | ||
| 149 | return self.input.stream | ||
| 150 | stream = property(_getStream) | ||
| 151 | |||
| 152 | def _getName(self): | ||
| 153 | return self.input.name | ||
| 154 | name = property(_getName) | ||
| 155 | |||
| 156 | def _getEndian(self): | ||
| 157 | return self.input.endian | ||
| 158 | endian = property(_getEndian) | ||
| 159 | |||
| 160 | def _getAddress(self): | ||
| 161 | if self._parent: | ||
| 162 | return self._parent._getFieldAddress(self.name) | ||
| 163 | else: | ||
| 164 | return 0 | ||
| 165 | address = property(_getAddress) | ||
| 166 | |||
| 167 | def _getAbsoluteAddress(self): | ||
| 168 | address = self.address | ||
| 169 | current = self._parent | ||
| 170 | while current: | ||
| 171 | address += current.address | ||
| 172 | current = current._parent | ||
| 173 | return address | ||
| 174 | absolute_address = property(_getAbsoluteAddress) | ||
| 175 | |||
| 176 | def hasValue(self): | ||
| 177 | return False | ||
| 178 | # return self._parent.input[self.name].hasValue() | ||
| 179 | |||
| 180 | def _getSize(self): | ||
| 181 | if self.is_altered: | ||
| 182 | return sum(field.size for field in self) | ||
| 183 | else: | ||
| 184 | return self.input.size | ||
| 185 | size = property(_getSize) | ||
| 186 | |||
| 187 | def _getPath(self): | ||
| 188 | return self.input.path | ||
| 189 | path = property(_getPath) | ||
| 190 | |||
| 191 | def _getOriginalField(self, name): | ||
| 192 | assert name in self.input | ||
| 193 | return self.input[name] | ||
| 194 | |||
| 195 | def _getFieldInputAddress(self, name): | ||
| 196 | """ | ||
| 197 | Absolute address of a field from the input field set. | ||
| 198 | """ | ||
| 199 | assert name in self.input | ||
| 200 | return self.input[name].absolute_address | ||
| 201 | |||
| 202 | def _getFieldAddress(self, name): | ||
| 203 | """ | ||
| 204 | Compute relative address of a field. The operation takes care of | ||
| 205 | deleted and resized fields. | ||
| 206 | """ | ||
| 207 | #assert name not in self._deleted | ||
| 208 | addr = 0 | ||
| 209 | for field in self: | ||
| 210 | if field.name == name: | ||
| 211 | return addr | ||
| 212 | addr += field.size | ||
| 213 | raise MissingField(self, name) | ||
| 214 | |||
| 215 | def _getItemByPath(self, path): | ||
| 216 | if not path[0]: | ||
| 217 | path = path[1:] | ||
| 218 | field = self | ||
| 219 | for name in path: | ||
| 220 | field = field[name] | ||
| 221 | return field | ||
| 222 | |||
| 223 | def __contains__(self, name): | ||
| 224 | try: | ||
| 225 | field = self[name] | ||
| 226 | return (field is not None) | ||
| 227 | except MissingField: | ||
| 228 | return False | ||
| 229 | |||
| 230 | def __getitem__(self, key): | ||
| 231 | """ | ||
| 232 | Create a weak reference to an editable field (EditableField) for the | ||
| 233 | field with specified name. If the field is removed later, using the | ||
| 234 | editable field will raise a weakref.ReferenceError exception. | ||
| 235 | |||
| 236 | May raise a MissingField error if the field doesn't exist in original | ||
| 237 | field set or it has been deleted. | ||
| 238 | """ | ||
| 239 | if "/" in key: | ||
| 240 | return self._getItemByPath(key.split("/")) | ||
| 241 | if isinstance(key, (int, long)): | ||
| 242 | raise EditorError("Integer index are not supported") | ||
| 243 | |||
| 244 | if (key in self._deleted) or (key not in self.input): | ||
| 245 | raise MissingField(self, key) | ||
| 246 | if key not in self._fields: | ||
| 247 | field = self.input[key] | ||
| 248 | if field.is_field_set: | ||
| 249 | self._fields[key] = createEditableFieldSet(self, field) | ||
| 250 | else: | ||
| 251 | self._fields[key] = createEditableField(self, field) | ||
| 252 | return weakref.proxy(self._fields[key]) | ||
| 253 | |||
| 254 | def __delitem__(self, name): | ||
| 255 | """ | ||
| 256 | Remove a field from the field set. May raise an MissingField exception | ||
| 257 | if the field has already been deleted. | ||
| 258 | """ | ||
| 259 | parts = name.partition('/') | ||
| 260 | if parts[2]: | ||
| 261 | fieldset = self[parts[0]] | ||
| 262 | del fieldset[parts[2]] | ||
| 263 | return | ||
| 264 | if name in self._deleted: | ||
| 265 | raise MissingField(self, name) | ||
| 266 | self._deleted.add(name) | ||
| 267 | if name in self._fields: | ||
| 268 | del self._fields[name] | ||
| 269 | |||
| 270 | def writeInto(self, output): | ||
| 271 | """ | ||
| 272 | Write the content if this field set into the output stream | ||
| 273 | (OutputStream). | ||
| 274 | """ | ||
| 275 | if not self.is_altered: | ||
| 276 | # Not altered: just copy bits/bytes | ||
| 277 | input = self.input | ||
| 278 | if input.size % 8: | ||
| 279 | output.copyBitsFrom(input.stream, | ||
| 280 | input.absolute_address, input.size, input.endian) | ||
| 281 | else: | ||
| 282 | output.copyBytesFrom(input.stream, | ||
| 283 | input.absolute_address, input.size//8) | ||
| 284 | else: | ||
| 285 | # Altered: call writeInto() method of each field | ||
| 286 | realaddr = 0 | ||
| 287 | for field in self: | ||
| 288 | field.writeInto(output) | ||
| 289 | realaddr += field.size | ||
| 290 | |||
| 291 | def _getValue(self): | ||
| 292 | raise EditorError('Field set "%s" has no value' % self.path) | ||
| 293 | def _setValue(self, value): | ||
| 294 | raise EditorError('Field set "%s" value is read only' % self.path) | ||
| 295 | value = property(_getValue, _setValue, "Value of field") | ||
| 296 | |||
| 297 | class EditableFloat(EditableFieldSet): | ||
| 298 | _value = None | ||
| 299 | |||
| 300 | def _isAltered(self): | ||
| 301 | return (self._value is not None) | ||
| 302 | is_altered = property(_isAltered) | ||
| 303 | |||
| 304 | def writeInto(self, output): | ||
| 305 | if self._value is not None: | ||
| 306 | self._write(output) | ||
| 307 | else: | ||
| 308 | EditableFieldSet.writeInto(self, output) | ||
| 309 | |||
| 310 | def _write(self, output): | ||
| 311 | format = self.input.struct_format | ||
| 312 | raw = struct.pack(format, self._value) | ||
| 313 | output.writeBytes(raw) | ||
| 314 | |||
| 315 | def _setValue(self, value): | ||
| 316 | self.parent._is_altered = True | ||
| 317 | self._value = value | ||
| 318 | value = property(EditableFieldSet._getValue, _setValue) | ||
| 319 | |||
| 320 | def createEditableFieldSet(parent, field): | ||
| 321 | cls = field.__class__ | ||
| 322 | # FIXME: Support Float80 | ||
| 323 | if cls in (Float32, Float64): | ||
| 324 | return EditableFloat(parent, field) | ||
| 325 | else: | ||
| 326 | return EditableFieldSet(parent, field) | ||
| 327 | |||
| 328 | class NewFieldSet(EditableFieldSet): | ||
| 329 | def __init__(self, parent, name): | ||
| 330 | EditableFieldSet.__init__(self, parent, None) | ||
| 331 | self._name = name | ||
| 332 | self._endian = parent.endian | ||
| 333 | |||
| 334 | def __iter__(self): | ||
| 335 | if None in self._inserted: | ||
| 336 | return iter(self._inserted[None]) | ||
| 337 | else: | ||
| 338 | raise StopIteration() | ||
| 339 | |||
| 340 | def _getName(self): | ||
| 341 | return self._name | ||
| 342 | name = property(_getName) | ||
| 343 | |||
| 344 | def _getEndian(self): | ||
| 345 | return self._endian | ||
| 346 | endian = property(_getEndian) | ||
| 347 | |||
| 348 | is_altered = property(lambda self: True) | ||
| 349 | |||
| 350 | def createEditor(fieldset): | ||
| 351 | return EditableFieldSet(None, fieldset) | ||
| 352 | |||
