Workbook.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. # -*- coding: windows-1252 -*-
  2. # Record Order in BIFF8
  3. # Workbook Globals Substream
  4. # BOF Type = workbook globals
  5. # Interface Header
  6. # MMS
  7. # Interface End
  8. # WRITEACCESS
  9. # CODEPAGE
  10. # DSF
  11. # TABID
  12. # FNGROUPCOUNT
  13. # Workbook Protection Block
  14. # WINDOWPROTECT
  15. # PROTECT
  16. # PASSWORD
  17. # PROT4REV
  18. # PROT4REVPASS
  19. # BACKUP
  20. # HIDEOBJ
  21. # WINDOW1
  22. # DATEMODE
  23. # PRECISION
  24. # REFRESHALL
  25. # BOOKBOOL
  26. # FONT +
  27. # FORMAT *
  28. # XF +
  29. # STYLE +
  30. # ? PALETTE
  31. # USESELFS
  32. #
  33. # BOUNDSHEET +
  34. #
  35. # COUNTRY
  36. # ? Link Table
  37. # SST
  38. # ExtSST
  39. # EOF
  40. from . import BIFFRecords
  41. from . import Style
  42. from .compat import unicode_type, int_types, basestring
  43. class Workbook(object):
  44. """
  45. This is a class representing a workbook and all its contents. When creating
  46. Excel files with xlwt, you will normally start by instantiating an
  47. object of this class.
  48. """
  49. #################################################################
  50. ## Constructor
  51. #################################################################
  52. def __init__(self, encoding='ascii', style_compression=0):
  53. self.encoding = encoding
  54. self.__owner = 'None'
  55. self.__country_code = None # 0x07 is Russia :-)
  56. self.__wnd_protect = 0
  57. self.__obj_protect = 0
  58. self.__protect = 0
  59. self.__backup_on_save = 0
  60. # for WINDOW1 record
  61. self.__hpos_twips = 0x01E0
  62. self.__vpos_twips = 0x005A
  63. self.__width_twips = 0x3FCF
  64. self.__height_twips = 0x2A4E
  65. self.__custom_palette_b8 = None
  66. self.__active_sheet = 0
  67. self.__first_tab_index = 0
  68. self.__selected_tabs = 0x01
  69. self.__tab_width_twips = 0x0258
  70. self.__wnd_hidden = 0
  71. self.__wnd_mini = 0
  72. self.__hscroll_visible = 1
  73. self.__vscroll_visible = 1
  74. self.__tabs_visible = 1
  75. self.__styles = Style.StyleCollection(style_compression)
  76. self.__dates_1904 = 0
  77. self.__use_cell_values = 1
  78. self.__sst = BIFFRecords.SharedStringTable(self.encoding)
  79. self.__worksheets = []
  80. self.__worksheet_idx_from_name = {}
  81. self.__sheet_refs = {}
  82. self._supbook_xref = {}
  83. self._xcall_xref = {}
  84. self._ownbook_supbookx = None
  85. self._ownbook_supbook_ref = None
  86. self._xcall_supbookx = None
  87. self._xcall_supbook_ref = None
  88. #################################################################
  89. ## Properties, "getters", "setters"
  90. #################################################################
  91. def get_style_stats(self):
  92. return self.__styles.stats[:]
  93. def set_owner(self, value):
  94. self.__owner = value
  95. def get_owner(self):
  96. return self.__owner
  97. owner = property(get_owner, set_owner)
  98. #################################################################
  99. def set_country_code(self, value):
  100. self.__country_code = value
  101. def get_country_code(self):
  102. return self.__country_code
  103. country_code = property(get_country_code, set_country_code)
  104. #################################################################
  105. def set_wnd_protect(self, value):
  106. self.__wnd_protect = int(value)
  107. def get_wnd_protect(self):
  108. return bool(self.__wnd_protect)
  109. wnd_protect = property(get_wnd_protect, set_wnd_protect)
  110. #################################################################
  111. def set_obj_protect(self, value):
  112. self.__obj_protect = int(value)
  113. def get_obj_protect(self):
  114. return bool(self.__obj_protect)
  115. obj_protect = property(get_obj_protect, set_obj_protect)
  116. #################################################################
  117. def set_protect(self, value):
  118. self.__protect = int(value)
  119. def get_protect(self):
  120. return bool(self.__protect)
  121. protect = property(get_protect, set_protect)
  122. #################################################################
  123. def set_backup_on_save(self, value):
  124. self.__backup_on_save = int(value)
  125. def get_backup_on_save(self):
  126. return bool(self.__backup_on_save)
  127. backup_on_save = property(get_backup_on_save, set_backup_on_save)
  128. #################################################################
  129. def set_hpos(self, value):
  130. self.__hpos_twips = value & 0xFFFF
  131. def get_hpos(self):
  132. return self.__hpos_twips
  133. hpos = property(get_hpos, set_hpos)
  134. #################################################################
  135. def set_vpos(self, value):
  136. self.__vpos_twips = value & 0xFFFF
  137. def get_vpos(self):
  138. return self.__vpos_twips
  139. vpos = property(get_vpos, set_vpos)
  140. #################################################################
  141. def set_width(self, value):
  142. self.__width_twips = value & 0xFFFF
  143. def get_width(self):
  144. return self.__width_twips
  145. width = property(get_width, set_width)
  146. #################################################################
  147. def set_height(self, value):
  148. self.__height_twips = value & 0xFFFF
  149. def get_height(self):
  150. return self.__height_twips
  151. height = property(get_height, set_height)
  152. #################################################################
  153. def set_active_sheet(self, value):
  154. self.__active_sheet = value & 0xFFFF
  155. self.__first_tab_index = self.__active_sheet
  156. def get_active_sheet(self):
  157. return self.__active_sheet
  158. active_sheet = property(get_active_sheet, set_active_sheet)
  159. #################################################################
  160. def set_tab_width(self, value):
  161. self.__tab_width_twips = value & 0xFFFF
  162. def get_tab_width(self):
  163. return self.__tab_width_twips
  164. tab_width = property(get_tab_width, set_tab_width)
  165. #################################################################
  166. def set_wnd_visible(self, value):
  167. self.__wnd_hidden = int(not value)
  168. def get_wnd_visible(self):
  169. return not bool(self.__wnd_hidden)
  170. wnd_visible = property(get_wnd_visible, set_wnd_visible)
  171. #################################################################
  172. def set_wnd_mini(self, value):
  173. self.__wnd_mini = int(value)
  174. def get_wnd_mini(self):
  175. return bool(self.__wnd_mini)
  176. wnd_mini = property(get_wnd_mini, set_wnd_mini)
  177. #################################################################
  178. def set_hscroll_visible(self, value):
  179. self.__hscroll_visible = int(value)
  180. def get_hscroll_visible(self):
  181. return bool(self.__hscroll_visible)
  182. hscroll_visible = property(get_hscroll_visible, set_hscroll_visible)
  183. #################################################################
  184. def set_vscroll_visible(self, value):
  185. self.__vscroll_visible = int(value)
  186. def get_vscroll_visible(self):
  187. return bool(self.__vscroll_visible)
  188. vscroll_visible = property(get_vscroll_visible, set_vscroll_visible)
  189. #################################################################
  190. def set_tabs_visible(self, value):
  191. self.__tabs_visible = int(value)
  192. def get_tabs_visible(self):
  193. return bool(self.__tabs_visible)
  194. tabs_visible = property(get_tabs_visible, set_tabs_visible)
  195. #################################################################
  196. def set_dates_1904(self, value):
  197. self.__dates_1904 = int(value)
  198. def get_dates_1904(self):
  199. return bool(self.__dates_1904)
  200. dates_1904 = property(get_dates_1904, set_dates_1904)
  201. #################################################################
  202. def set_use_cell_values(self, value):
  203. self.__use_cell_values = int(value)
  204. def get_use_cell_values(self):
  205. return bool(self.__use_cell_values)
  206. use_cell_values = property(get_use_cell_values, set_use_cell_values)
  207. #################################################################
  208. def get_default_style(self):
  209. return self.__styles.default_style
  210. default_style = property(get_default_style)
  211. #################################################################
  212. def set_colour_RGB(self, colour_index, red, green, blue):
  213. if not(8 <= colour_index <= 63):
  214. raise Exception("set_colour_RGB: colour_index (%d) not in range(8, 64)" %
  215. colour_index)
  216. if min(red, green, blue) < 0 or max(red, green, blue) > 255:
  217. raise Exception("set_colour_RGB: colour values (%d,%d,%d) must be in range(0, 256)"
  218. % (red, green, blue))
  219. if self.__custom_palette_b8 is None:
  220. self.__custom_palette_b8 = list(Style.excel_default_palette_b8)
  221. # User-defined Palette starts at colour index 8,
  222. # so subtract 8 from colour_index when placing in palette
  223. palette_index = colour_index - 8
  224. self.__custom_palette_b8[palette_index] = red << 24 | green << 16 | blue << 8
  225. ##################################################################
  226. ## Methods
  227. ##################################################################
  228. def add_style(self, style):
  229. return self.__styles.add(style)
  230. def add_font(self, font):
  231. return self.__styles.add_font(font)
  232. def add_str(self, s):
  233. return self.__sst.add_str(s)
  234. def del_str(self, sst_idx):
  235. self.__sst.del_str(sst_idx)
  236. def str_index(self, s):
  237. return self.__sst.str_index(s)
  238. def add_rt(self, rt):
  239. return self.__sst.add_rt(rt)
  240. def rt_index(self, rt):
  241. return self.__sst.rt_index(rt)
  242. def add_sheet(self, sheetname, cell_overwrite_ok=False):
  243. """
  244. This method is used to create Worksheets in a Workbook.
  245. :param sheetname:
  246. The name to use for this sheet, as it will appear in the
  247. tabs at the bottom of the Excel application.
  248. :param cell_overwrite_ok:
  249. If ``True``, cells in the added worksheet will not raise an
  250. exception if written to more than once.
  251. :return:
  252. The :class:`~xlwt.Worksheet.Worksheet` that was added.
  253. """
  254. from . import Utils
  255. from .Worksheet import Worksheet
  256. if not isinstance(sheetname, unicode_type):
  257. sheetname = sheetname.decode(self.encoding)
  258. if not Utils.valid_sheet_name(sheetname):
  259. raise Exception("invalid worksheet name %r" % sheetname)
  260. lower_name = sheetname.lower()
  261. if lower_name in self.__worksheet_idx_from_name:
  262. raise Exception("duplicate worksheet name %r" % sheetname)
  263. self.__worksheet_idx_from_name[lower_name] = len(self.__worksheets)
  264. self.__worksheets.append(Worksheet(sheetname, self, cell_overwrite_ok))
  265. return self.__worksheets[-1]
  266. def get_sheet(self, sheet):
  267. if isinstance(sheet, int_types):
  268. return self.__worksheets[sheet]
  269. elif isinstance(sheet, basestring):
  270. sheetnum = self.sheet_index(sheet)
  271. return self.__worksheets[sheetnum]
  272. else:
  273. raise Exception("sheet must be integer or string")
  274. def sheet_index(self, sheetname):
  275. try:
  276. sheetnum = self.__worksheet_idx_from_name[sheetname.lower()]
  277. except KeyError:
  278. self.raise_bad_sheetname(sheetname)
  279. return sheetnum
  280. def raise_bad_sheetname(self, sheetname):
  281. raise Exception("Formula: unknown sheet name %s" % sheetname)
  282. def convert_sheetindex(self, strg_ref, n_sheets):
  283. idx = int(strg_ref)
  284. if 0 <= idx < n_sheets:
  285. return idx
  286. msg = "Formula: sheet index (%s) >= number of sheets (%d)" % (strg_ref, n_sheets)
  287. raise Exception(msg)
  288. def _get_supbook_index(self, tag):
  289. if tag in self._supbook_xref:
  290. return self._supbook_xref[tag]
  291. self._supbook_xref[tag] = idx = len(self._supbook_xref)
  292. return idx
  293. def setup_ownbook(self):
  294. self._ownbook_supbookx = self._get_supbook_index(('ownbook', 0))
  295. self._ownbook_supbook_ref = None
  296. reference = (self._ownbook_supbookx, 0xFFFE, 0xFFFE)
  297. if reference in self.__sheet_refs:
  298. raise Exception("can't happen")
  299. self.__sheet_refs[reference] = self._ownbook_supbook_ref = len(self.__sheet_refs)
  300. def setup_xcall(self):
  301. self._xcall_supbookx = self._get_supbook_index(('xcall', 0))
  302. self._xcall_supbook_ref = None
  303. reference = (self._xcall_supbookx, 0xFFFE, 0xFFFE)
  304. if reference in self.__sheet_refs:
  305. raise Exception("can't happen")
  306. self.__sheet_refs[reference] = self._xcall_supbook_ref = len(self.__sheet_refs)
  307. def add_sheet_reference(self, formula):
  308. patches = []
  309. n_sheets = len(self.__worksheets)
  310. sheet_refs, xcall_refs = formula.get_references()
  311. for ref0, ref1, offset in sheet_refs:
  312. if not ref0.isdigit():
  313. try:
  314. ref0n = self.__worksheet_idx_from_name[ref0.lower()]
  315. except KeyError:
  316. self.raise_bad_sheetname(ref0)
  317. else:
  318. ref0n = self.convert_sheetindex(ref0, n_sheets)
  319. if ref1 == ref0:
  320. ref1n = ref0n
  321. elif not ref1.isdigit():
  322. try:
  323. ref1n = self.__worksheet_idx_from_name[ref1.lower()]
  324. except KeyError:
  325. self.raise_bad_sheetname(ref1)
  326. else:
  327. ref1n = self.convert_sheetindex(ref1, n_sheets)
  328. if ref1n < ref0n:
  329. msg = "Formula: sheets out of order; %r:%r -> (%d, %d)" \
  330. % (ref0, ref1, ref0n, ref1n)
  331. raise Exception(msg)
  332. if self._ownbook_supbookx is None:
  333. self.setup_ownbook()
  334. reference = (self._ownbook_supbookx, ref0n, ref1n)
  335. if reference in self.__sheet_refs:
  336. patches.append((offset, self.__sheet_refs[reference]))
  337. else:
  338. nrefs = len(self.__sheet_refs)
  339. if nrefs > 65535:
  340. raise Exception('More than 65536 inter-sheet references')
  341. self.__sheet_refs[reference] = nrefs
  342. patches.append((offset, nrefs))
  343. for funcname, offset in xcall_refs:
  344. if self._ownbook_supbookx is None:
  345. self.setup_ownbook()
  346. if self._xcall_supbookx is None:
  347. self.setup_xcall()
  348. # print funcname, self._supbook_xref
  349. patches.append((offset, self._xcall_supbook_ref))
  350. if not isinstance(funcname, unicode_type):
  351. funcname = funcname.decode(self.encoding)
  352. if funcname in self._xcall_xref:
  353. idx = self._xcall_xref[funcname]
  354. else:
  355. self._xcall_xref[funcname] = idx = len(self._xcall_xref)
  356. patches.append((offset + 2, idx + 1))
  357. formula.patch_references(patches)
  358. ##################################################################
  359. ## BIFF records generation
  360. ##################################################################
  361. def __bof_rec(self):
  362. return BIFFRecords.Biff8BOFRecord(BIFFRecords.Biff8BOFRecord.BOOK_GLOBAL).get()
  363. def __eof_rec(self):
  364. return BIFFRecords.EOFRecord().get()
  365. def __intf_hdr_rec(self):
  366. return BIFFRecords.InteraceHdrRecord().get()
  367. def __intf_end_rec(self):
  368. return BIFFRecords.InteraceEndRecord().get()
  369. def __intf_mms_rec(self):
  370. return BIFFRecords.MMSRecord().get()
  371. def __write_access_rec(self):
  372. return BIFFRecords.WriteAccessRecord(self.__owner).get()
  373. def __wnd_protect_rec(self):
  374. return BIFFRecords.WindowProtectRecord(self.__wnd_protect).get()
  375. def __obj_protect_rec(self):
  376. return BIFFRecords.ObjectProtectRecord(self.__obj_protect).get()
  377. def __protect_rec(self):
  378. return BIFFRecords.ProtectRecord(self.__protect).get()
  379. def __password_rec(self):
  380. return BIFFRecords.PasswordRecord().get()
  381. def __prot4rev_rec(self):
  382. return BIFFRecords.Prot4RevRecord().get()
  383. def __prot4rev_pass_rec(self):
  384. return BIFFRecords.Prot4RevPassRecord().get()
  385. def __backup_rec(self):
  386. return BIFFRecords.BackupRecord(self.__backup_on_save).get()
  387. def __hide_obj_rec(self):
  388. return BIFFRecords.HideObjRecord().get()
  389. def __window1_rec(self):
  390. flags = 0
  391. flags |= (self.__wnd_hidden) << 0
  392. flags |= (self.__wnd_mini) << 1
  393. flags |= (self.__hscroll_visible) << 3
  394. flags |= (self.__vscroll_visible) << 4
  395. flags |= (self.__tabs_visible) << 5
  396. return BIFFRecords.Window1Record(self.__hpos_twips, self.__vpos_twips,
  397. self.__width_twips, self.__height_twips,
  398. flags,
  399. self.__active_sheet, self.__first_tab_index,
  400. self.__selected_tabs, self.__tab_width_twips).get()
  401. def __codepage_rec(self):
  402. return BIFFRecords.CodepageBiff8Record().get()
  403. def __country_rec(self):
  404. if not self.__country_code:
  405. return b''
  406. return BIFFRecords.CountryRecord(self.__country_code, self.__country_code).get()
  407. def __dsf_rec(self):
  408. return BIFFRecords.DSFRecord().get()
  409. def __tabid_rec(self):
  410. return BIFFRecords.TabIDRecord(len(self.__worksheets)).get()
  411. def __fngroupcount_rec(self):
  412. return BIFFRecords.FnGroupCountRecord().get()
  413. def __datemode_rec(self):
  414. return BIFFRecords.DateModeRecord(self.__dates_1904).get()
  415. def __precision_rec(self):
  416. return BIFFRecords.PrecisionRecord(self.__use_cell_values).get()
  417. def __refresh_all_rec(self):
  418. return BIFFRecords.RefreshAllRecord().get()
  419. def __bookbool_rec(self):
  420. return BIFFRecords.BookBoolRecord().get()
  421. def __all_fonts_num_formats_xf_styles_rec(self):
  422. return self.__styles.get_biff_data()
  423. def __palette_rec(self):
  424. if self.__custom_palette_b8 is None:
  425. return b''
  426. info = BIFFRecords.PaletteRecord(self.__custom_palette_b8).get()
  427. return info
  428. def __useselfs_rec(self):
  429. return BIFFRecords.UseSelfsRecord().get()
  430. def __boundsheets_rec(self, data_len_before, data_len_after, sheet_biff_lens):
  431. # .................................
  432. # BOUNDSEHEET0
  433. # BOUNDSEHEET1
  434. # BOUNDSEHEET2
  435. # ..................................
  436. # WORKSHEET0
  437. # WORKSHEET1
  438. # WORKSHEET2
  439. boundsheets_len = 0
  440. for sheet in self.__worksheets:
  441. boundsheets_len += len(BIFFRecords.BoundSheetRecord(
  442. 0x00, sheet.visibility, sheet.name, self.encoding
  443. ).get())
  444. start = data_len_before + boundsheets_len + data_len_after
  445. result = b''
  446. for sheet_biff_len, sheet in zip(sheet_biff_lens, self.__worksheets):
  447. result += BIFFRecords.BoundSheetRecord(
  448. start, sheet.visibility, sheet.name, self.encoding
  449. ).get()
  450. start += sheet_biff_len
  451. return result
  452. def __all_links_rec(self):
  453. pieces = []
  454. temp = [(idx, tag) for tag, idx in self._supbook_xref.items()]
  455. temp.sort()
  456. for idx, tag in temp:
  457. stype, snum = tag
  458. if stype == 'ownbook':
  459. rec = BIFFRecords.InternalReferenceSupBookRecord(len(self.__worksheets)).get()
  460. pieces.append(rec)
  461. elif stype == 'xcall':
  462. rec = BIFFRecords.XcallSupBookRecord().get()
  463. pieces.append(rec)
  464. temp = [(idx, name) for name, idx in self._xcall_xref.items()]
  465. temp.sort()
  466. for idx, name in temp:
  467. rec = BIFFRecords.ExternnameRecord(
  468. options=0, index=0, name=name, fmla='\x02\x00\x1c\x17').get()
  469. pieces.append(rec)
  470. else:
  471. raise Exception('unknown supbook stype %r' % stype)
  472. if len(self.__sheet_refs) > 0:
  473. # get references in index order
  474. temp = [(idx, ref) for ref, idx in self.__sheet_refs.items()]
  475. temp.sort()
  476. temp = [ref for idx, ref in temp]
  477. externsheet_record = BIFFRecords.ExternSheetRecord(temp).get()
  478. pieces.append(externsheet_record)
  479. return b''.join(pieces)
  480. def __sst_rec(self):
  481. return self.__sst.get_biff_record()
  482. def __ext_sst_rec(self, abs_stream_pos):
  483. return b''
  484. #return BIFFRecords.ExtSSTRecord(abs_stream_pos, self.sst_record.str_placement,
  485. #self.sst_record.portions_len).get()
  486. def get_biff_data(self):
  487. before = b''
  488. before += self.__bof_rec()
  489. before += self.__intf_hdr_rec()
  490. before += self.__intf_mms_rec()
  491. before += self.__intf_end_rec()
  492. before += self.__write_access_rec()
  493. before += self.__codepage_rec()
  494. before += self.__dsf_rec()
  495. before += self.__tabid_rec()
  496. before += self.__fngroupcount_rec()
  497. before += self.__wnd_protect_rec()
  498. before += self.__protect_rec()
  499. before += self.__obj_protect_rec()
  500. before += self.__password_rec()
  501. before += self.__prot4rev_rec()
  502. before += self.__prot4rev_pass_rec()
  503. before += self.__backup_rec()
  504. before += self.__hide_obj_rec()
  505. before += self.__window1_rec()
  506. before += self.__datemode_rec()
  507. before += self.__precision_rec()
  508. before += self.__refresh_all_rec()
  509. before += self.__bookbool_rec()
  510. before += self.__all_fonts_num_formats_xf_styles_rec()
  511. before += self.__palette_rec()
  512. before += self.__useselfs_rec()
  513. country = self.__country_rec()
  514. all_links = self.__all_links_rec()
  515. shared_str_table = self.__sst_rec()
  516. after = country + all_links + shared_str_table
  517. ext_sst = self.__ext_sst_rec(0) # need fake cause we need calc stream pos
  518. eof = self.__eof_rec()
  519. self.__worksheets[self.__active_sheet].selected = True
  520. sheets = b''
  521. sheet_biff_lens = []
  522. for sheet in self.__worksheets:
  523. data = sheet.get_biff_data()
  524. sheets += data
  525. sheet_biff_lens.append(len(data))
  526. bundlesheets = self.__boundsheets_rec(len(before), len(after)+len(ext_sst)+len(eof), sheet_biff_lens)
  527. sst_stream_pos = len(before) + len(bundlesheets) + len(country) + len(all_links)
  528. ext_sst = self.__ext_sst_rec(sst_stream_pos)
  529. return before + bundlesheets + after + ext_sst + eof + sheets
  530. def save(self, filename_or_stream):
  531. """
  532. This method is used to save the Workbook to a file in native Excel
  533. format.
  534. :param filename_or_stream:
  535. This can be a string containing a filename of
  536. the file, in which case the excel file is saved to disk using the name
  537. provided. It can also be a stream object with a write method, such as
  538. a :class:`~io.StringIO`, in which case the data for the excel
  539. file is written to the stream.
  540. """
  541. from . import CompoundDoc
  542. doc = CompoundDoc.XlsDoc()
  543. doc.save(filename_or_stream, self.get_biff_data())