3 "Structure and Field classes for declaring structures "
5 from __future__ import absolute_import
6 import struct as _struct
11 _buffer = buffer # save builtin buffer for clobbered situations
15 """Represent a Structure field.
21 def __init__(self, format, name, default=None, help=None, count=1):
22 self.format = format # See the struct documentation
27 self.total_count = _numpy.prod(count)
30 class Structure (_struct.Struct):
31 """Represent a C structure.
33 A convenient wrapper around struct.Struct that uses Fields and
34 adds dict-handling methods for transparent name assignment.
43 Represent the C structure::
53 >>> from pprint import pprint
54 >>> thing = Structure(name='thing',
55 ... fields=[Field('h', 'version'), Field('l', 'size', count=3)])
56 >>> thing.set_byte_order('>')
57 >>> b = array.array('b', range(2+4*3))
58 >>> d = thing.unpack_dict_from(buffer=b)
60 {'size': array([ 33752069, 101124105, 168496141]), 'version': 1}
61 >>> [hex(x) for x in d['size']]
62 ['0x2030405L', '0x6070809L', '0xa0b0c0dL']
64 You can even get fancy with multi-dimensional arrays.
66 >>> thing = Structure(name='thing',
67 ... fields=[Field('h', 'version'), Field('l', 'size', count=(3,2))])
68 >>> thing.set_byte_order('>')
69 >>> b = array.array('b', range(2+4*3*2))
70 >>> d = thing.unpack_dict_from(buffer=b)
74 {'size': array([[ 33752069, 101124105],
75 [168496141, 235868177],
76 [303240213, 370612249]]),
79 def __init__(self, name, fields, byte_order='='):
80 # '=' for native byte order, standard size and alignment
81 # See http://docs.python.org/library/struct for details
84 self.set_byte_order(byte_order)
89 def set_byte_order(self, byte_order):
90 """Allow changing the format byte_order on the fly.
92 if (hasattr(self, 'format') and self.format != None
93 and self.format.startswith(byte_order)):
94 return # no need to change anything
96 for field in self.fields:
97 format.extend([field.format]*field.total_count)
98 super(Structure, self).__init__(
99 format=byte_order+''.join(format).replace('P', 'L'))
101 def _flatten_args(self, args):
102 # handle Field.count > 0
104 for a,f in zip(args, self.fields):
105 if f.total_count > 1:
111 def _unflatten_args(self, args):
112 # handle Field.count > 0
115 for f in self.fields:
116 if f.total_count > 1:
117 data = _numpy.array(args[i:i+f.total_count])
118 data = data.reshape(f.count)
119 unflat_args.append(data)
121 unflat_args.append(args[i])
125 def pack(self, *args):
126 return super(Structure, self)(*self._flatten_args(args))
128 def pack_into(self, buffer, offset, *args):
129 return super(Structure, self).pack_into(
130 buffer, offset, *self._flatten_args(args))
132 def _clean_dict(self, dict):
133 for f in self.fields:
134 if f.name not in dict:
135 if f.default != None:
136 dict[f.name] = f.default
138 raise ValueError('{} field not set for {}'.format(
139 f.name, self.__class__.__name__))
142 def pack_dict(self, dict):
143 dict = self._clean_dict(dict)
144 return self.pack(*[dict[f.name] for f in self.fields])
146 def pack_dict_into(self, buffer, offset, dict={}):
147 dict = self._clean_dict(dict)
148 return self.pack_into(buffer, offset,
149 *[dict[f.name] for f in self.fields])
151 def unpack(self, string):
152 return self._unflatten_args(
153 super(Structure, self).unpack(string))
155 def unpack_from(self, buffer, offset=0):
157 args = super(Structure, self).unpack_from(buffer, offset)
158 except _struct.error as e:
159 if not self.name in ('WaveHeader2', 'WaveHeader5'):
161 # HACK! For WaveHeader5, when npnts is 0, wData is
162 # optional. If we couldn't unpack the structure, fill in
163 # wData with zeros and try again, asserting that npnts is
165 if len(buffer) - offset < self.size:
166 # missing wData? Pad with zeros
167 buffer += _buffer('\x00'*(self.size + offset - len(buffer)))
168 args = super(Structure, self).unpack_from(buffer, offset)
169 unpacked = self._unflatten_args(args)
170 data = dict(zip([f.name for f in self.fields],
172 assert data['npnts'] == 0, data['npnts']
173 return self._unflatten_args(args)
175 def unpack_dict(self, string):
176 return dict(zip([f.name for f in self.fields],
177 self.unpack(string)))
179 def unpack_dict_from(self, buffer, offset=0):
180 return dict(zip([f.name for f in self.fields],
181 self.unpack_from(buffer, offset)))