idl.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. # IDLSave - a python module to read IDL 'save' files
  2. # Copyright (c) 2010 Thomas P. Robitaille
  3. # Many thanks to Craig Markwardt for publishing the Unofficial Format
  4. # Specification for IDL .sav files, without which this Python module would not
  5. # exist (http://cow.physics.wisc.edu/~craigm/idl/savefmt).
  6. # This code was developed by with permission from ITT Visual Information
  7. # Systems. IDL(r) is a registered trademark of ITT Visual Information Systems,
  8. # Inc. for their Interactive Data Language software.
  9. # Permission is hereby granted, free of charge, to any person obtaining a
  10. # copy of this software and associated documentation files (the "Software"),
  11. # to deal in the Software without restriction, including without limitation
  12. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  13. # and/or sell copies of the Software, and to permit persons to whom the
  14. # Software is furnished to do so, subject to the following conditions:
  15. # The above copyright notice and this permission notice shall be included in
  16. # all copies or substantial portions of the Software.
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  23. # DEALINGS IN THE SOFTWARE.
  24. from __future__ import division, print_function, absolute_import
  25. __all__ = ['readsav']
  26. import struct
  27. import numpy as np
  28. from numpy.compat import asstr
  29. import tempfile
  30. import zlib
  31. import warnings
  32. # Define the different data types that can be found in an IDL save file
  33. DTYPE_DICT = {1: '>u1',
  34. 2: '>i2',
  35. 3: '>i4',
  36. 4: '>f4',
  37. 5: '>f8',
  38. 6: '>c8',
  39. 7: '|O',
  40. 8: '|O',
  41. 9: '>c16',
  42. 10: '|O',
  43. 11: '|O',
  44. 12: '>u2',
  45. 13: '>u4',
  46. 14: '>i8',
  47. 15: '>u8'}
  48. # Define the different record types that can be found in an IDL save file
  49. RECTYPE_DICT = {0: "START_MARKER",
  50. 1: "COMMON_VARIABLE",
  51. 2: "VARIABLE",
  52. 3: "SYSTEM_VARIABLE",
  53. 6: "END_MARKER",
  54. 10: "TIMESTAMP",
  55. 12: "COMPILED",
  56. 13: "IDENTIFICATION",
  57. 14: "VERSION",
  58. 15: "HEAP_HEADER",
  59. 16: "HEAP_DATA",
  60. 17: "PROMOTE64",
  61. 19: "NOTICE",
  62. 20: "DESCRIPTION"}
  63. # Define a dictionary to contain structure definitions
  64. STRUCT_DICT = {}
  65. def _align_32(f):
  66. '''Align to the next 32-bit position in a file'''
  67. pos = f.tell()
  68. if pos % 4 != 0:
  69. f.seek(pos + 4 - pos % 4)
  70. return
  71. def _skip_bytes(f, n):
  72. '''Skip `n` bytes'''
  73. f.read(n)
  74. return
  75. def _read_bytes(f, n):
  76. '''Read the next `n` bytes'''
  77. return f.read(n)
  78. def _read_byte(f):
  79. '''Read a single byte'''
  80. return np.uint8(struct.unpack('>B', f.read(4)[:1])[0])
  81. def _read_long(f):
  82. '''Read a signed 32-bit integer'''
  83. return np.int32(struct.unpack('>l', f.read(4))[0])
  84. def _read_int16(f):
  85. '''Read a signed 16-bit integer'''
  86. return np.int16(struct.unpack('>h', f.read(4)[2:4])[0])
  87. def _read_int32(f):
  88. '''Read a signed 32-bit integer'''
  89. return np.int32(struct.unpack('>i', f.read(4))[0])
  90. def _read_int64(f):
  91. '''Read a signed 64-bit integer'''
  92. return np.int64(struct.unpack('>q', f.read(8))[0])
  93. def _read_uint16(f):
  94. '''Read an unsigned 16-bit integer'''
  95. return np.uint16(struct.unpack('>H', f.read(4)[2:4])[0])
  96. def _read_uint32(f):
  97. '''Read an unsigned 32-bit integer'''
  98. return np.uint32(struct.unpack('>I', f.read(4))[0])
  99. def _read_uint64(f):
  100. '''Read an unsigned 64-bit integer'''
  101. return np.uint64(struct.unpack('>Q', f.read(8))[0])
  102. def _read_float32(f):
  103. '''Read a 32-bit float'''
  104. return np.float32(struct.unpack('>f', f.read(4))[0])
  105. def _read_float64(f):
  106. '''Read a 64-bit float'''
  107. return np.float64(struct.unpack('>d', f.read(8))[0])
  108. class Pointer(object):
  109. '''Class used to define pointers'''
  110. def __init__(self, index):
  111. self.index = index
  112. return
  113. class ObjectPointer(Pointer):
  114. '''Class used to define object pointers'''
  115. pass
  116. def _read_string(f):
  117. '''Read a string'''
  118. length = _read_long(f)
  119. if length > 0:
  120. chars = _read_bytes(f, length)
  121. _align_32(f)
  122. chars = asstr(chars)
  123. else:
  124. chars = ''
  125. return chars
  126. def _read_string_data(f):
  127. '''Read a data string (length is specified twice)'''
  128. length = _read_long(f)
  129. if length > 0:
  130. length = _read_long(f)
  131. string_data = _read_bytes(f, length)
  132. _align_32(f)
  133. else:
  134. string_data = ''
  135. return string_data
  136. def _read_data(f, dtype):
  137. '''Read a variable with a specified data type'''
  138. if dtype == 1:
  139. if _read_int32(f) != 1:
  140. raise Exception("Error occurred while reading byte variable")
  141. return _read_byte(f)
  142. elif dtype == 2:
  143. return _read_int16(f)
  144. elif dtype == 3:
  145. return _read_int32(f)
  146. elif dtype == 4:
  147. return _read_float32(f)
  148. elif dtype == 5:
  149. return _read_float64(f)
  150. elif dtype == 6:
  151. real = _read_float32(f)
  152. imag = _read_float32(f)
  153. return np.complex64(real + imag * 1j)
  154. elif dtype == 7:
  155. return _read_string_data(f)
  156. elif dtype == 8:
  157. raise Exception("Should not be here - please report this")
  158. elif dtype == 9:
  159. real = _read_float64(f)
  160. imag = _read_float64(f)
  161. return np.complex128(real + imag * 1j)
  162. elif dtype == 10:
  163. return Pointer(_read_int32(f))
  164. elif dtype == 11:
  165. return ObjectPointer(_read_int32(f))
  166. elif dtype == 12:
  167. return _read_uint16(f)
  168. elif dtype == 13:
  169. return _read_uint32(f)
  170. elif dtype == 14:
  171. return _read_int64(f)
  172. elif dtype == 15:
  173. return _read_uint64(f)
  174. else:
  175. raise Exception("Unknown IDL type: %i - please report this" % dtype)
  176. def _read_structure(f, array_desc, struct_desc):
  177. '''
  178. Read a structure, with the array and structure descriptors given as
  179. `array_desc` and `structure_desc` respectively.
  180. '''
  181. nrows = array_desc['nelements']
  182. columns = struct_desc['tagtable']
  183. dtype = []
  184. for col in columns:
  185. if col['structure'] or col['array']:
  186. dtype.append(((col['name'].lower(), col['name']), np.object_))
  187. else:
  188. if col['typecode'] in DTYPE_DICT:
  189. dtype.append(((col['name'].lower(), col['name']),
  190. DTYPE_DICT[col['typecode']]))
  191. else:
  192. raise Exception("Variable type %i not implemented" %
  193. col['typecode'])
  194. structure = np.recarray((nrows, ), dtype=dtype)
  195. for i in range(nrows):
  196. for col in columns:
  197. dtype = col['typecode']
  198. if col['structure']:
  199. structure[col['name']][i] = _read_structure(f,
  200. struct_desc['arrtable'][col['name']],
  201. struct_desc['structtable'][col['name']])
  202. elif col['array']:
  203. structure[col['name']][i] = _read_array(f, dtype,
  204. struct_desc['arrtable'][col['name']])
  205. else:
  206. structure[col['name']][i] = _read_data(f, dtype)
  207. # Reshape structure if needed
  208. if array_desc['ndims'] > 1:
  209. dims = array_desc['dims'][:int(array_desc['ndims'])]
  210. dims.reverse()
  211. structure = structure.reshape(dims)
  212. return structure
  213. def _read_array(f, typecode, array_desc):
  214. '''
  215. Read an array of type `typecode`, with the array descriptor given as
  216. `array_desc`.
  217. '''
  218. if typecode in [1, 3, 4, 5, 6, 9, 13, 14, 15]:
  219. if typecode == 1:
  220. nbytes = _read_int32(f)
  221. if nbytes != array_desc['nbytes']:
  222. warnings.warn("Not able to verify number of bytes from header")
  223. # Read bytes as numpy array
  224. array = np.frombuffer(f.read(array_desc['nbytes']),
  225. dtype=DTYPE_DICT[typecode])
  226. elif typecode in [2, 12]:
  227. # These are 2 byte types, need to skip every two as they are not packed
  228. array = np.frombuffer(f.read(array_desc['nbytes']*2),
  229. dtype=DTYPE_DICT[typecode])[1::2]
  230. else:
  231. # Read bytes into list
  232. array = []
  233. for i in range(array_desc['nelements']):
  234. dtype = typecode
  235. data = _read_data(f, dtype)
  236. array.append(data)
  237. array = np.array(array, dtype=np.object_)
  238. # Reshape array if needed
  239. if array_desc['ndims'] > 1:
  240. dims = array_desc['dims'][:int(array_desc['ndims'])]
  241. dims.reverse()
  242. array = array.reshape(dims)
  243. # Go to next alignment position
  244. _align_32(f)
  245. return array
  246. def _read_record(f):
  247. '''Function to read in a full record'''
  248. record = {'rectype': _read_long(f)}
  249. nextrec = _read_uint32(f)
  250. nextrec += _read_uint32(f) * 2**32
  251. _skip_bytes(f, 4)
  252. if not record['rectype'] in RECTYPE_DICT:
  253. raise Exception("Unknown RECTYPE: %i" % record['rectype'])
  254. record['rectype'] = RECTYPE_DICT[record['rectype']]
  255. if record['rectype'] in ["VARIABLE", "HEAP_DATA"]:
  256. if record['rectype'] == "VARIABLE":
  257. record['varname'] = _read_string(f)
  258. else:
  259. record['heap_index'] = _read_long(f)
  260. _skip_bytes(f, 4)
  261. rectypedesc = _read_typedesc(f)
  262. if rectypedesc['typecode'] == 0:
  263. if nextrec == f.tell():
  264. record['data'] = None # Indicates NULL value
  265. else:
  266. raise ValueError("Unexpected type code: 0")
  267. else:
  268. varstart = _read_long(f)
  269. if varstart != 7:
  270. raise Exception("VARSTART is not 7")
  271. if rectypedesc['structure']:
  272. record['data'] = _read_structure(f, rectypedesc['array_desc'],
  273. rectypedesc['struct_desc'])
  274. elif rectypedesc['array']:
  275. record['data'] = _read_array(f, rectypedesc['typecode'],
  276. rectypedesc['array_desc'])
  277. else:
  278. dtype = rectypedesc['typecode']
  279. record['data'] = _read_data(f, dtype)
  280. elif record['rectype'] == "TIMESTAMP":
  281. _skip_bytes(f, 4*256)
  282. record['date'] = _read_string(f)
  283. record['user'] = _read_string(f)
  284. record['host'] = _read_string(f)
  285. elif record['rectype'] == "VERSION":
  286. record['format'] = _read_long(f)
  287. record['arch'] = _read_string(f)
  288. record['os'] = _read_string(f)
  289. record['release'] = _read_string(f)
  290. elif record['rectype'] == "IDENTIFICATON":
  291. record['author'] = _read_string(f)
  292. record['title'] = _read_string(f)
  293. record['idcode'] = _read_string(f)
  294. elif record['rectype'] == "NOTICE":
  295. record['notice'] = _read_string(f)
  296. elif record['rectype'] == "DESCRIPTION":
  297. record['description'] = _read_string_data(f)
  298. elif record['rectype'] == "HEAP_HEADER":
  299. record['nvalues'] = _read_long(f)
  300. record['indices'] = []
  301. for i in range(record['nvalues']):
  302. record['indices'].append(_read_long(f))
  303. elif record['rectype'] == "COMMONBLOCK":
  304. record['nvars'] = _read_long(f)
  305. record['name'] = _read_string(f)
  306. record['varnames'] = []
  307. for i in range(record['nvars']):
  308. record['varnames'].append(_read_string(f))
  309. elif record['rectype'] == "END_MARKER":
  310. record['end'] = True
  311. elif record['rectype'] == "UNKNOWN":
  312. warnings.warn("Skipping UNKNOWN record")
  313. elif record['rectype'] == "SYSTEM_VARIABLE":
  314. warnings.warn("Skipping SYSTEM_VARIABLE record")
  315. else:
  316. raise Exception("record['rectype']=%s not implemented" %
  317. record['rectype'])
  318. f.seek(nextrec)
  319. return record
  320. def _read_typedesc(f):
  321. '''Function to read in a type descriptor'''
  322. typedesc = {'typecode': _read_long(f), 'varflags': _read_long(f)}
  323. if typedesc['varflags'] & 2 == 2:
  324. raise Exception("System variables not implemented")
  325. typedesc['array'] = typedesc['varflags'] & 4 == 4
  326. typedesc['structure'] = typedesc['varflags'] & 32 == 32
  327. if typedesc['structure']:
  328. typedesc['array_desc'] = _read_arraydesc(f)
  329. typedesc['struct_desc'] = _read_structdesc(f)
  330. elif typedesc['array']:
  331. typedesc['array_desc'] = _read_arraydesc(f)
  332. return typedesc
  333. def _read_arraydesc(f):
  334. '''Function to read in an array descriptor'''
  335. arraydesc = {'arrstart': _read_long(f)}
  336. if arraydesc['arrstart'] == 8:
  337. _skip_bytes(f, 4)
  338. arraydesc['nbytes'] = _read_long(f)
  339. arraydesc['nelements'] = _read_long(f)
  340. arraydesc['ndims'] = _read_long(f)
  341. _skip_bytes(f, 8)
  342. arraydesc['nmax'] = _read_long(f)
  343. arraydesc['dims'] = []
  344. for d in range(arraydesc['nmax']):
  345. arraydesc['dims'].append(_read_long(f))
  346. elif arraydesc['arrstart'] == 18:
  347. warnings.warn("Using experimental 64-bit array read")
  348. _skip_bytes(f, 8)
  349. arraydesc['nbytes'] = _read_uint64(f)
  350. arraydesc['nelements'] = _read_uint64(f)
  351. arraydesc['ndims'] = _read_long(f)
  352. _skip_bytes(f, 8)
  353. arraydesc['nmax'] = 8
  354. arraydesc['dims'] = []
  355. for d in range(arraydesc['nmax']):
  356. v = _read_long(f)
  357. if v != 0:
  358. raise Exception("Expected a zero in ARRAY_DESC")
  359. arraydesc['dims'].append(_read_long(f))
  360. else:
  361. raise Exception("Unknown ARRSTART: %i" % arraydesc['arrstart'])
  362. return arraydesc
  363. def _read_structdesc(f):
  364. '''Function to read in a structure descriptor'''
  365. structdesc = {}
  366. structstart = _read_long(f)
  367. if structstart != 9:
  368. raise Exception("STRUCTSTART should be 9")
  369. structdesc['name'] = _read_string(f)
  370. predef = _read_long(f)
  371. structdesc['ntags'] = _read_long(f)
  372. structdesc['nbytes'] = _read_long(f)
  373. structdesc['predef'] = predef & 1
  374. structdesc['inherits'] = predef & 2
  375. structdesc['is_super'] = predef & 4
  376. if not structdesc['predef']:
  377. structdesc['tagtable'] = []
  378. for t in range(structdesc['ntags']):
  379. structdesc['tagtable'].append(_read_tagdesc(f))
  380. for tag in structdesc['tagtable']:
  381. tag['name'] = _read_string(f)
  382. structdesc['arrtable'] = {}
  383. for tag in structdesc['tagtable']:
  384. if tag['array']:
  385. structdesc['arrtable'][tag['name']] = _read_arraydesc(f)
  386. structdesc['structtable'] = {}
  387. for tag in structdesc['tagtable']:
  388. if tag['structure']:
  389. structdesc['structtable'][tag['name']] = _read_structdesc(f)
  390. if structdesc['inherits'] or structdesc['is_super']:
  391. structdesc['classname'] = _read_string(f)
  392. structdesc['nsupclasses'] = _read_long(f)
  393. structdesc['supclassnames'] = []
  394. for s in range(structdesc['nsupclasses']):
  395. structdesc['supclassnames'].append(_read_string(f))
  396. structdesc['supclasstable'] = []
  397. for s in range(structdesc['nsupclasses']):
  398. structdesc['supclasstable'].append(_read_structdesc(f))
  399. STRUCT_DICT[structdesc['name']] = structdesc
  400. else:
  401. if not structdesc['name'] in STRUCT_DICT:
  402. raise Exception("PREDEF=1 but can't find definition")
  403. structdesc = STRUCT_DICT[structdesc['name']]
  404. return structdesc
  405. def _read_tagdesc(f):
  406. '''Function to read in a tag descriptor'''
  407. tagdesc = {'offset': _read_long(f)}
  408. if tagdesc['offset'] == -1:
  409. tagdesc['offset'] = _read_uint64(f)
  410. tagdesc['typecode'] = _read_long(f)
  411. tagflags = _read_long(f)
  412. tagdesc['array'] = tagflags & 4 == 4
  413. tagdesc['structure'] = tagflags & 32 == 32
  414. tagdesc['scalar'] = tagdesc['typecode'] in DTYPE_DICT
  415. # Assume '10'x is scalar
  416. return tagdesc
  417. def _replace_heap(variable, heap):
  418. if isinstance(variable, Pointer):
  419. while isinstance(variable, Pointer):
  420. if variable.index == 0:
  421. variable = None
  422. else:
  423. if variable.index in heap:
  424. variable = heap[variable.index]
  425. else:
  426. warnings.warn("Variable referenced by pointer not found "
  427. "in heap: variable will be set to None")
  428. variable = None
  429. replace, new = _replace_heap(variable, heap)
  430. if replace:
  431. variable = new
  432. return True, variable
  433. elif isinstance(variable, np.core.records.recarray):
  434. # Loop over records
  435. for ir, record in enumerate(variable):
  436. replace, new = _replace_heap(record, heap)
  437. if replace:
  438. variable[ir] = new
  439. return False, variable
  440. elif isinstance(variable, np.core.records.record):
  441. # Loop over values
  442. for iv, value in enumerate(variable):
  443. replace, new = _replace_heap(value, heap)
  444. if replace:
  445. variable[iv] = new
  446. return False, variable
  447. elif isinstance(variable, np.ndarray):
  448. # Loop over values if type is np.object_
  449. if variable.dtype.type is np.object_:
  450. for iv in range(variable.size):
  451. replace, new = _replace_heap(variable.item(iv), heap)
  452. if replace:
  453. variable.itemset(iv, new)
  454. return False, variable
  455. else:
  456. return False, variable
  457. class AttrDict(dict):
  458. '''
  459. A case-insensitive dictionary with access via item, attribute, and call
  460. notations:
  461. >>> d = AttrDict()
  462. >>> d['Variable'] = 123
  463. >>> d['Variable']
  464. 123
  465. >>> d.Variable
  466. 123
  467. >>> d.variable
  468. 123
  469. >>> d('VARIABLE')
  470. 123
  471. '''
  472. def __init__(self, init={}):
  473. dict.__init__(self, init)
  474. def __getitem__(self, name):
  475. return super(AttrDict, self).__getitem__(name.lower())
  476. def __setitem__(self, key, value):
  477. return super(AttrDict, self).__setitem__(key.lower(), value)
  478. __getattr__ = __getitem__
  479. __setattr__ = __setitem__
  480. __call__ = __getitem__
  481. def readsav(file_name, idict=None, python_dict=False,
  482. uncompressed_file_name=None, verbose=False):
  483. """
  484. Read an IDL .sav file.
  485. Parameters
  486. ----------
  487. file_name : str
  488. Name of the IDL save file.
  489. idict : dict, optional
  490. Dictionary in which to insert .sav file variables.
  491. python_dict : bool, optional
  492. By default, the object return is not a Python dictionary, but a
  493. case-insensitive dictionary with item, attribute, and call access
  494. to variables. To get a standard Python dictionary, set this option
  495. to True.
  496. uncompressed_file_name : str, optional
  497. This option only has an effect for .sav files written with the
  498. /compress option. If a file name is specified, compressed .sav
  499. files are uncompressed to this file. Otherwise, readsav will use
  500. the `tempfile` module to determine a temporary filename
  501. automatically, and will remove the temporary file upon successfully
  502. reading it in.
  503. verbose : bool, optional
  504. Whether to print out information about the save file, including
  505. the records read, and available variables.
  506. Returns
  507. -------
  508. idl_dict : AttrDict or dict
  509. If `python_dict` is set to False (default), this function returns a
  510. case-insensitive dictionary with item, attribute, and call access
  511. to variables. If `python_dict` is set to True, this function
  512. returns a Python dictionary with all variable names in lowercase.
  513. If `idict` was specified, then variables are written to the
  514. dictionary specified, and the updated dictionary is returned.
  515. """
  516. # Initialize record and variable holders
  517. records = []
  518. if python_dict or idict:
  519. variables = {}
  520. else:
  521. variables = AttrDict()
  522. # Open the IDL file
  523. f = open(file_name, 'rb')
  524. # Read the signature, which should be 'SR'
  525. signature = _read_bytes(f, 2)
  526. if signature != b'SR':
  527. raise Exception("Invalid SIGNATURE: %s" % signature)
  528. # Next, the record format, which is '\x00\x04' for normal .sav
  529. # files, and '\x00\x06' for compressed .sav files.
  530. recfmt = _read_bytes(f, 2)
  531. if recfmt == b'\x00\x04':
  532. pass
  533. elif recfmt == b'\x00\x06':
  534. if verbose:
  535. print("IDL Save file is compressed")
  536. if uncompressed_file_name:
  537. fout = open(uncompressed_file_name, 'w+b')
  538. else:
  539. fout = tempfile.NamedTemporaryFile(suffix='.sav')
  540. if verbose:
  541. print(" -> expanding to %s" % fout.name)
  542. # Write header
  543. fout.write(b'SR\x00\x04')
  544. # Cycle through records
  545. while True:
  546. # Read record type
  547. rectype = _read_long(f)
  548. fout.write(struct.pack('>l', int(rectype)))
  549. # Read position of next record and return as int
  550. nextrec = _read_uint32(f)
  551. nextrec += _read_uint32(f) * 2**32
  552. # Read the unknown 4 bytes
  553. unknown = f.read(4)
  554. # Check if the end of the file has been reached
  555. if RECTYPE_DICT[rectype] == 'END_MARKER':
  556. fout.write(struct.pack('>I', int(nextrec) % 2**32))
  557. fout.write(struct.pack('>I', int((nextrec - (nextrec % 2**32)) / 2**32)))
  558. fout.write(unknown)
  559. break
  560. # Find current position
  561. pos = f.tell()
  562. # Decompress record
  563. rec_string = zlib.decompress(f.read(nextrec-pos))
  564. # Find new position of next record
  565. nextrec = fout.tell() + len(rec_string) + 12
  566. # Write out record
  567. fout.write(struct.pack('>I', int(nextrec % 2**32)))
  568. fout.write(struct.pack('>I', int((nextrec - (nextrec % 2**32)) / 2**32)))
  569. fout.write(unknown)
  570. fout.write(rec_string)
  571. # Close the original compressed file
  572. f.close()
  573. # Set f to be the decompressed file, and skip the first four bytes
  574. f = fout
  575. f.seek(4)
  576. else:
  577. raise Exception("Invalid RECFMT: %s" % recfmt)
  578. # Loop through records, and add them to the list
  579. while True:
  580. r = _read_record(f)
  581. records.append(r)
  582. if 'end' in r:
  583. if r['end']:
  584. break
  585. # Close the file
  586. f.close()
  587. # Find heap data variables
  588. heap = {}
  589. for r in records:
  590. if r['rectype'] == "HEAP_DATA":
  591. heap[r['heap_index']] = r['data']
  592. # Find all variables
  593. for r in records:
  594. if r['rectype'] == "VARIABLE":
  595. replace, new = _replace_heap(r['data'], heap)
  596. if replace:
  597. r['data'] = new
  598. variables[r['varname'].lower()] = r['data']
  599. if verbose:
  600. # Print out timestamp info about the file
  601. for record in records:
  602. if record['rectype'] == "TIMESTAMP":
  603. print("-"*50)
  604. print("Date: %s" % record['date'])
  605. print("User: %s" % record['user'])
  606. print("Host: %s" % record['host'])
  607. break
  608. # Print out version info about the file
  609. for record in records:
  610. if record['rectype'] == "VERSION":
  611. print("-"*50)
  612. print("Format: %s" % record['format'])
  613. print("Architecture: %s" % record['arch'])
  614. print("Operating System: %s" % record['os'])
  615. print("IDL Version: %s" % record['release'])
  616. break
  617. # Print out identification info about the file
  618. for record in records:
  619. if record['rectype'] == "IDENTIFICATON":
  620. print("-"*50)
  621. print("Author: %s" % record['author'])
  622. print("Title: %s" % record['title'])
  623. print("ID Code: %s" % record['idcode'])
  624. break
  625. # Print out descriptions saved with the file
  626. for record in records:
  627. if record['rectype'] == "DESCRIPTION":
  628. print("-"*50)
  629. print("Description: %s" % record['description'])
  630. break
  631. print("-"*50)
  632. print("Successfully read %i records of which:" %
  633. (len(records)))
  634. # Create convenience list of record types
  635. rectypes = [r['rectype'] for r in records]
  636. for rt in set(rectypes):
  637. if rt != 'END_MARKER':
  638. print(" - %i are of type %s" % (rectypes.count(rt), rt))
  639. print("-"*50)
  640. if 'VARIABLE' in rectypes:
  641. print("Available variables:")
  642. for var in variables:
  643. print(" - %s [%s]" % (var, type(variables[var])))
  644. print("-"*50)
  645. if idict:
  646. for var in variables:
  647. idict[var] = variables[var]
  648. return idict
  649. else:
  650. return variables