1 """sstruct.py -- SuperStruct 2 3 Higher level layer on top of the struct module, enabling to 4 bind names to struct elements. The interface is similar to 5 struct, except the objects passed and returned are not tuples 6 (or argument lists), but dictionaries or instances. 7 8 Just like struct, we use fmt strings to describe a data 9 structure, except we use one line per element. Lines are 10 separated by newlines or semi-colons. Each line contains 11 either one of the special struct characters ('@', '=', '<', 12 '>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f'). 13 Repetitions, like the struct module offers them are not useful 14 in this context, except for fixed length strings (eg. 'myInt:5h' 15 is not allowed but 'myString:5s' is). The 'x' fmt character 16 (pad byte) is treated as 'special', since it is by definition 17 anonymous. Extra whitespace is allowed everywhere. 18 19 The sstruct module offers one feature that the "normal" struct 20 module doesn't: support for fixed point numbers. These are spelled 21 as "n.mF", where n is the number of bits before the point, and m 22 the number of bits after the point. Fixed point numbers get 23 converted to floats. 24 25 pack(fmt, object): 26 'object' is either a dictionary or an instance (or actually 27 anything that has a __dict__ attribute). If it is a dictionary, 28 its keys are used for names. If it is an instance, it's 29 attributes are used to grab struct elements from. Returns 30 a string containing the data. 31 32 unpack(fmt, data, object=None) 33 If 'object' is omitted (or None), a new dictionary will be 34 returned. If 'object' is a dictionary, it will be used to add 35 struct elements to. If it is an instance (or in fact anything 36 that has a __dict__ attribute), an attribute will be added for 37 each struct element. In the latter two cases, 'object' itself 38 is returned. 39 40 unpack2(fmt, data, object=None) 41 Convenience function. Same as unpack, except data may be longer 42 than needed. The returned value is a tuple: (object, leftoverdata). 43 44 calcsize(fmt) 45 like struct.calcsize(), but uses our own fmt strings: 46 it returns the size of the data in bytes. 47 """ 48 49 from __future__ import print_function, division, absolute_import 50 from fontTools.misc.py23 import * 51 from fontTools.misc.fixedTools import fixedToFloat as fi2fl, floatToFixed as fl2fi 52 import struct 53 import re 54 55 __version__ = "1.2" 56 __copyright__ = "Copyright 1998, Just van Rossum <just (at] letterror.com>" 57 58 59 class Error(Exception): 60 pass 61 62 def pack(fmt, obj): 63 formatstring, names, fixes = getformat(fmt) 64 elements = [] 65 if not isinstance(obj, dict): 66 obj = obj.__dict__ 67 for name in names: 68 value = obj[name] 69 if name in fixes: 70 # fixed point conversion 71 value = fl2fi(value, fixes[name]) 72 elif isinstance(value, basestring): 73 value = tobytes(value) 74 elements.append(value) 75 data = struct.pack(*(formatstring,) + tuple(elements)) 76 return data 77 78 def unpack(fmt, data, obj=None): 79 if obj is None: 80 obj = {} 81 data = tobytes(data) 82 formatstring, names, fixes = getformat(fmt) 83 if isinstance(obj, dict): 84 d = obj 85 else: 86 d = obj.__dict__ 87 elements = struct.unpack(formatstring, data) 88 for i in range(len(names)): 89 name = names[i] 90 value = elements[i] 91 if name in fixes: 92 # fixed point conversion 93 value = fi2fl(value, fixes[name]) 94 elif isinstance(value, bytes): 95 try: 96 value = tostr(value) 97 except UnicodeDecodeError: 98 pass 99 d[name] = value 100 return obj 101 102 def unpack2(fmt, data, obj=None): 103 length = calcsize(fmt) 104 return unpack(fmt, data[:length], obj), data[length:] 105 106 def calcsize(fmt): 107 formatstring, names, fixes = getformat(fmt) 108 return struct.calcsize(formatstring) 109 110 111 # matches "name:formatchar" (whitespace is allowed) 112 _elementRE = re.compile( 113 "\s*" # whitespace 114 "([A-Za-z_][A-Za-z_0-9]*)" # name (python identifier) 115 "\s*:\s*" # whitespace : whitespace 116 "([cbBhHiIlLqQfd]|[0-9]+[ps]|" # formatchar... 117 "([0-9]+)\.([0-9]+)(F))" # ...formatchar 118 "\s*" # whitespace 119 "(#.*)?$" # [comment] + end of string 120 ) 121 122 # matches the special struct fmt chars and 'x' (pad byte) 123 _extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$") 124 125 # matches an "empty" string, possibly containing whitespace and/or a comment 126 _emptyRE = re.compile("\s*(#.*)?$") 127 128 _fixedpointmappings = { 129 8: "b", 130 16: "h", 131 32: "l"} 132 133 _formatcache = {} 134 135 def getformat(fmt): 136 try: 137 formatstring, names, fixes = _formatcache[fmt] 138 except KeyError: 139 lines = re.split("[\n;]", fmt) 140 formatstring = "" 141 names = [] 142 fixes = {} 143 for line in lines: 144 if _emptyRE.match(line): 145 continue 146 m = _extraRE.match(line) 147 if m: 148 formatchar = m.group(1) 149 if formatchar != 'x' and formatstring: 150 raise Error("a special fmt char must be first") 151 else: 152 m = _elementRE.match(line) 153 if not m: 154 raise Error("syntax error in fmt: '%s'" % line) 155 name = m.group(1) 156 names.append(name) 157 formatchar = m.group(2) 158 if m.group(3): 159 # fixed point 160 before = int(m.group(3)) 161 after = int(m.group(4)) 162 bits = before + after 163 if bits not in [8, 16, 32]: 164 raise Error("fixed point must be 8, 16 or 32 bits long") 165 formatchar = _fixedpointmappings[bits] 166 assert m.group(5) == "F" 167 fixes[name] = after 168 formatstring = formatstring + formatchar 169 _formatcache[fmt] = formatstring, names, fixes 170 return formatstring, names, fixes 171 172 def _test(): 173 fmt = """ 174 # comments are allowed 175 > # big endian (see documentation for struct) 176 # empty lines are allowed: 177 178 ashort: h 179 along: l 180 abyte: b # a byte 181 achar: c 182 astr: 5s 183 afloat: f; adouble: d # multiple "statements" are allowed 184 afixed: 16.16F 185 """ 186 187 print('size:', calcsize(fmt)) 188 189 class foo(object): 190 pass 191 192 i = foo() 193 194 i.ashort = 0x7fff 195 i.along = 0x7fffffff 196 i.abyte = 0x7f 197 i.achar = "a" 198 i.astr = "12345" 199 i.afloat = 0.5 200 i.adouble = 0.5 201 i.afixed = 1.5 202 203 data = pack(fmt, i) 204 print('data:', repr(data)) 205 print(unpack(fmt, data)) 206 i2 = foo() 207 unpack(fmt, data, i2) 208 print(vars(i2)) 209 210 if __name__ == "__main__": 211 _test() 212