pax_global_header00006660000000000000000000000064137646430000014515gustar00rootroot0000000000000052 comment=b8d573e11ec149da695d695c81a156232b89a949 xlrd-2.0.1/000077500000000000000000000000001376464300000124665ustar00rootroot00000000000000xlrd-2.0.1/.carthorse.yml000066400000000000000000000003421376464300000152600ustar00rootroot00000000000000carthorse: version-from: setup.py tag-format: "{version}" when: - version-not-tagged actions: - run: "sudo pip install -e .[build]" - run: "twine upload -u __token__ -p $PYPI_TOKEN dist/*" - create-tag xlrd-2.0.1/.circleci/000077500000000000000000000000001376464300000143215ustar00rootroot00000000000000xlrd-2.0.1/.circleci/config.yml000066400000000000000000000040431376464300000163120ustar00rootroot00000000000000version: 2.1 orbs: python: cjw296/python-ci@2.1 jobs: coverage: docker: - image: circleci/python:3.8 steps: - checkout - attach_workspace: at: coverage_output - run: name: "Check coverage" command: | sudo pip install coverage coverage combine coverage_output/ bash <(curl -s https://codecov.io/bash) check-package: parameters: image: type: string docker: - image: << parameters.image >> steps: - python/check-package: package: "xlrd" test: - run: name: "Check Import" command: python -c "import xlrd" - run: name: "Check no XLS in wheel" command: "! unzip -l dist/*.whl | egrep '.xlsx?$'" - run: name: "Check no XLS in source dist" command: "! tar tzf dist/*.tar.gz | egrep '.xlsx?$'" common: &common jobs: - python/pip-run-tests: matrix: parameters: image: - circleci/python:2.7 - circleci/python:3.6 - circleci/python:3.9 - coverage: name: coverage requires: - python/pip-run-tests - python/pip-docs: name: docs requires: - coverage - python/pip-setuptools-build-package: name: package requires: - docs filters: branches: only: master - check-package: matrix: parameters: image: - circleci/python:2.7 - circleci/python:3.9 requires: - package - python/release: name: release config: .carthorse.yml requires: - check-package filters: branches: only: master workflows: push: <<: *common periodic: <<: *common triggers: - schedule: cron: "0 0 11 * *" filters: branches: only: master xlrd-2.0.1/.coveragerc000066400000000000000000000002051376464300000146040ustar00rootroot00000000000000[run] source = xlrd,scripts,tests [report] exclude_lines = # the original exclude pragma: no cover # debug stuff if DEBUG: xlrd-2.0.1/.gitignore000066400000000000000000000001621376464300000144550ustar00rootroot00000000000000/build /dist *.egg-info build/ _build/ *.pyc /.coverage /.tox /*.xml /htmlcov MANIFEST /bin .Python /include /lib xlrd-2.0.1/.readthedocs.yml000066400000000000000000000002261376464300000155540ustar00rootroot00000000000000version: 2 python: version: 3.8 install: - method: pip path: . extra_requirements: - docs sphinx: fail_on_warning: true xlrd-2.0.1/CHANGELOG.rst000066400000000000000000000450701376464300000145150ustar00rootroot00000000000000Changes ======= 2.0.1 (11 December 2020) ------------------------ - Use the README as the long description on PyPI. 2.0.0 (11 December 2020) ------------------------ - Remove support for anything other than ``.xls`` files. - Remove support for ``psyco``. - Change the default encoding used when no ``CODEPAGE`` record can be found from ``ascii`` to ``iso-8859-1``. - Add support for iterating over :class:`~xlrd.book.Book` objects. - Add support for item access from :class:`~xlrd.book.Book` objects, where integer indices and string sheet names are supported. - Non-unicode spaces are now stripped from the "last author" information. - Workbook corruption errors can now be ignored using the ``ignore_workbook_corruption`` option to :class:`~xlrd.open_workbook`. - Handle ``WRITEACCESS`` records with invalid trailing characters. - Officially support Python 3.8 and 3.9. Thanks to the following for their contributions to this release: - Jon Dufresne - Tore Lundqvist - nayyarv - Michael Davis - skonik 1.2.0 (15 December 2018) ------------------------ - Added support for Python 3.7. - Added optional support for defusedxml to help mitigate exploits. - Automatically convert ``~`` in file paths to the current user's home directory. - Removed ``examples`` directory from the installed package. They are still available in the source distribution. - Fixed ``time.clock()`` deprecation warning. 1.1.0 (22 August 2017) ---------------------- - Fix for parsing of merged cells containing a single cell reference in xlsx files. - Fix for "invalid literal for int() with base 10: 'true'" when reading some xlsx files. - Make xldate_as_datetime available to import direct from xlrd. - Build universal wheels. - Sphinx documentation. - Document the problem with XML vulnerabilities in xlsx files and mitigation measures. - Fix :class:`NameError` on ``has_defaults is not defined``. - Some whitespace and code style tweaks. - Make example in README compatible with both Python 2 and 3. - Add default value for cells containing errors that causeed parsing of some xlsx files to fail. - Add Python 3.6 to the list of supported Python versions, drop 3.3 and 2.6. - Use generator expressions to avoid unnecessary lists in memory. - Document unicode encoding used in Excel files from Excel 97 onwards. - Report hyperlink errors in R1C1 syntax. Thanks to the following for their contributions to this release: - icereval@gmail.com - Daniel Rech - Ville Skyttä - Yegor Yefremov - Maxime Lorant - Alexandr N Zamaraev - Zhaorong Ma - Jon Dufresne - Chris McIntyre - coltleese@gmail.com - Ivan Masá 1.0.0 (2 June 2016) ------------------- - Official support, such as it is, is now for 2.6, 2.7, 3.3+ - Fixes a bug in looking up non-lowercase sheet filenames by ensuring that the sheet targets are transformed the same way as the component_names dict keys. - Fixes a bug for ``ragged_rows=False`` when merged cells increases the number of columns in the sheet. This requires all rows to be extended to ensure equal row lengths that match the number of columns in the sheet. - Fixes to enable reading of SAP-generated .xls files. - support BIFF4 files with missing FORMAT records. - support files with missing WINDOW2 record. - Empty cells are now always unicode strings, they were a bytestring on Python 2 and a unicode string on Python 3. - Fix for ```` ``inlineStr`` attribute without ```` child. - Fix for a zoom of ``None`` causing problems on Python 3. - Fix parsing of bad dimensions. - Fix xlsx sheet to comments relationship. Thanks to the following for their contributions to this release: - Lars-Erik Hannelius - Deshi Xiao - Stratos Moro - Volker Diels-Grabsch - John McNamara - Ville Skyttä - Patrick Fuller - Dragon Dave McKee - Gunnlaugur Þór Briem 0.9.4 (14 July 2015) -------------------- - Automated tests are now run on Python 3.4 - Use ``ElementTree.iter()`` if available, instead of the deprecated ``getiterator()`` when parsing xlsx files. - Fix #106 : Exception Value: unorderable types: Name() < Name() - Create row generator expression with Sheet.get_rows() - Fix for forward slash file separator and lowercase names within xlsx internals. Thanks to the following for their contributions to this release: - Corey Farwell - Jonathan Kamens - Deepak N - Brandon R. Stoner - John McNamara 0.9.3 (8 Apr 2014) ------------------ - Github issue #49 - Github issue #64 - skip meaningless chunk of 4 zero bytes between two otherwise-valid BIFF records - Github issue #61 - fix updating of escapement attribute of Font objects read from workbooks. - Implemented ``Sheet.visibility`` for xlsx files - Ignore anchors (``$``) in cell references - Dropped support for Python 2.5 and earlier, Python 2.6 is now the earliest Python release supported - Read xlsx merged cell elements. - Read cell comments in .xlsx files. - Added xldate_as_datetime() function to convert from Excel serial date/time to datetime.datetime object. Thanks to the following for their contributions to this release: - John Machin - Caleb Epstein - Martin Panter - John McNamara - Gunnlaugur Þór Briem - Stephen Lewis 0.9.2 (9 Apr 2013) ------------------ - Fix some packaging issues that meant docs and examples were missing from the tarball. - Fixed a small but serious regression that caused problems opening .xlsx files. 0.9.1 (5 Apr 2013) ------------------ - Many fixes bugs in Python 3 support. - Fix bug where ragged rows needed fixing when formatting info was being parsed. - Improved handling of aberrant Excel 4.0 Worksheet files. - Various bug fixes. - Simplify a lot of the distribution packaging. - Remove unused and duplicate imports. Thanks to the following for their contributions to this release: - Thomas Kluyver 0.9.0 (31 Jan 2013) ------------------- - Support for Python 3.2+ - Many new unit test added. - Continuous integration tests are now run. - Various bug fixes. Special thanks to Thomas Kluyver and Martin Panter for their work on Python 3 compatibility. Thanks to Manfred Moitzi for re-licensing his unit tests so we could include them. Thanks to the following for their contributions to this release: - "holm" - Victor Safronovich - Ross Jones 0.8.0 (22 Aug 2012) ------------------- - More work-arounds for broken source files. - Support for reading .xlsx files. - Drop support for Python 2.5 and older. 0.7.8 (7 June 2012) ------------------- - Ignore superfluous zero bytes at end of xls OBJECT record. - Fix assertion error when reading file with xlwt-written bitmap. 0.7.7 (13 Apr 2012) ------------------- - More packaging changes, this time to support 2to3. 0.7.6 (3 Apr 2012) ------------------ - Fix more packaging issues. 0.7.5 (3 Apr 2012) ------------------ - Fix packaging issue that missed ``version.txt`` from the distributions. 0.7.4 (2 Apr 2012) ------------------ - More tolerance of out-of-spec files. - Fix bugs reading long text formula results. 0.7.3 (28 Feb 2012) ------------------- - Packaging and documentation updates. 0.7.2 (21 Feb 2012) ------------------- - Tolerant handling of files with extra zero bytes at end of NUMBER record. Sample provided by Jan Kraus. - Added access to cell notes/comments. Many cross-references added to Sheet class docs. - Added code to extract hyperlink (HLINK) records. Based on a patch supplied by John Morrisey. - Extraction of rich text formatting info based on code supplied by Nathan van Gheem. - added handling of BIFF2 WINDOW2 record. - Included modified version of page breaks patch from Sam Listopad. - Added reading of the PANE record. - Reading SCL record. New attribute ``Sheet.scl_mag_factor``. - Lots of bug fixes. - Added ``ragged_rows`` functionality. 0.7.1 (31 May 2009) ------------------- - Backed out "slash'n'burn" of sheet resources in unload_sheet(). Fixed problem with STYLE records on some Mac Excel files. - quieten warnings - Integrated on_demand patch by Armando Serrano Lombillo 0.7.0 (11 March 2009) --------------------- + colname utility function now supports more than 256 columns. + Fix bug where BIFF record type 0x806 was being regarded as a formula opcode. + Ignore PALETTE record when formatting_info is false. + Tolerate up to 4 bytes trailing junk on PALETTE record. + Fixed bug in unused utility function xldate_from_date_tuple which affected some years after 2099. + Added code for inspecting as-yet-unused record types: FILEPASS, TXO, NOTE. + Added inspection code for add_in function calls. + Added support for unnumbered biff_dump (better for doing diffs). + ignore distutils cruft + Avoid assertion error in compdoc when -1 used instead of -2 for first_SID of empty SCSS + Make version numbers match up. + Enhanced recovery from out-of-order/missing/wrong CODEPAGE record. + Added Name.area2d convenience method. + Avoided some checking of XF info when formatting_info is false. + Minor changes in preparation for XLSX support. + remove duplicate files that were out of date. + Basic support for Excel 2.0 + Decouple Book init & load. + runxlrd: minor fix for xfc. + More Excel 2.x work. + is_date_format() tweak. + Better detection of IronPython. + Better error message (including first 8 bytes of file) when file is not in a supported format. + More BIFF2 formatting: ROW, COLWIDTH, and COLUMNDEFAULT records; + finished stage 1 of XF records. + More work on supporting BIFF2 (Excel 2.x) files. + Added support for Excel 2.x (BIFF2) files. Data only, no formatting info. Alpha. + Wasn't coping with EXTERNSHEET record followed by CONTINUE record(s). + Allow for BIFF2/3-style FORMAT record in BIFF4/8 file + Avoid crash when zero-length Unicode string missing options byte. + Warning message if sector sizes are extremely large. + Work around corrupt STYLE record + Added missing entry for blank cell type to ctype_text + Added "fonts" command to runxlrd script + Warning: style XF whose parent XF index != 0xFFF + Logfile arg wasn't being passed from open_workbook to compdoc.CompDoc. 0.6.1 (10 June 2007) --------------------- + Version number updated to 0.6.1 + Documented runxlrd.py commands in its usage message. Changed commands: dump to biff_dump, count_records to biff_count. 0.6.1a5 ------- + Bug fixed: Missing "<" in a struct.unpack call means can't open files on bigendian platforms. Discovered by "Mihalis". + Removed antique undocumented Book.get_name_dict method and experimental "trimming" facility. + Meaningful exception instead of IndexError if a SAT (sector allocation table) is corrupted. + If no CODEPAGE record in pre-8.0 file, assume ascii and keep going (instead of raising exception). 0.6.1a4 ------- + At least one source of XLS files writes parent style XF records *after* the child cell XF records that refer to them, triggering IndexError in 0.5.2 and AssertionError in later versions. Reported with sample file by Todd O'Bryan. Fixed by changing to two-pass processing of XF records. + Formatting info in pre-BIFF8 files: Ensured appropriate defaults and lossless conversions to make the info BIFF8-compatible. Fixed bug in extracting the "used" flags. + Fixed problems discovered with opening test files from Planmaker 2006 (http://www.softmaker.com/english/ofwcomp_en.htm): (1) Four files have reduced size of PALETTE record (51 and 32 colours; Excel writes 56 always). xlrd now emits a NOTE to the logfile and continues. (2) FORMULA records use the Excel 2.x record code 0x0021 instead of 0x0221. xlrd now continues silently. (3) In two files, at the OLE2 compound document level, the internal directory says that the length of the Short-Stream Container Stream is 16384 bytes, but the actual contents are 11264 and 9728 bytes respectively. xlrd now emits a WARNING to the logfile and continues. + After discussion with Daniel Rentz, the concept of two lists of XF (eXtended Format) objects (raw_xf_list and computed_xf_list) has been abandoned. There is now a single list, called xf_list 0.6.1a3 ------- + Added Book.sheets ... for sheetx, sheet in enumerate(book.sheets): + Formatting info: extraction of sheet-level flags from WINDOW2 record, and sheet.visibility from BOUNDSHEET record. Added Macintosh- only Font attributes "outline" and "shadow'. 0.6.1a2 ------- + Added extraction of merged cells info. + pyExcelerator uses "general" instead of "General" for the generic "number format". Worked around. + Crystal Reports writes "WORKBOOK" in the OLE2 Compound Document directory instead of "Workbook". Changed to case-insensitive directory search. Reported by Vic Simkus. 0.6.1a1 (18 Dec 2006) --------------------- + Added formatting information for cells (font, "number format", background, border, alignment and protection) and rows/columns (height/width etc). To save memory and time for those who don't need it, this information is extracted only if formatting_info=1 is supplied to the open_workbook() function. The cell records BLANK and MULBLANKS which contain no data, only formatting information, will continue to be ignored in the default (no formatting info) case. + Ralph Heimburger reported a problem with xlrd being intolerant about an Excel 4.0 file (created by "some web app") with a DIMENSIONS record that omitted Microsoft's usual padding with 2 unused bytes. Fixed. 0.6.0a4 (not released) ---------------------- + Added extraction of human-readable formulas from NAME records. + Worked around OOo Calc writing 9-byte BOOLERR records instead of 8. Reported by Rory Campbell-Lange. + This history file converted to descending chronological order and HTML format. 0.6.0a3 (19 Sept 2006) ---------------------- + Names: minor bugfixes; added script xlrdnameAPIdemo.py + ROW records were being used as additional hints for sizing memory requirements. In some files the ROW records overstate the number of used columns, and/or there are ROW records for rows that have no data in them. This would cause xlrd to report sheet.ncols and/or sheet.nrows as larger than reasonably expected. Change: ROW records are ignored. The number of columns/rows is based solely on the highest column/row index seen in non-empty data records. Empty data records (types BLANK and MULBLANKS) which contain no data, only formatting information, have always been ignored, and this will continue. Consequence: trailing rows and columns which contain only empty cells will vanish. 0.6.0a2 (13 Sept 2006) ---------------------- + Fixed a bug reported by Rory Campbell-Lange.: "open failed"; incorrect assumptions about the layout of array formulas which return strings. + Further work on defined names, especially the API. 0.6.0a1 (8 Sept 2006) --------------------- + Sheet objects have two new convenience methods: col_values(colx, start_rowx=0, end_rowx=None) and the corresponding col_types. Suggested by Dennis O'Brien. + BIFF 8 file missing its CODEPAGE record: xlrd will now assume utf_16_le encoding (the only possibility) and keep going. + Older files missing a CODEPAGE record: an exception will be raised. Thanks to Sergey Krushinsky for a sample file. The open_workbook() function has a new argument (encoding_override) which can be used if the CODEPAGE record is missing or incorrect (for example, codepage=1251 but the data is actually encoded in koi8_r). The runxlrd.py script takes a corresponding -e argument, for example -e cp1251 + Further work done on parsing "number formats". Thanks to Chris Withers for the ``"General_)"`` example. + Excel 97 introduced the concept of row and column labels, defined by Insert > Name > Labels. The ranges containing the labels are now exposed as the Sheet attributes row_label_ranges and col_label_ranges. + The major effort in this 0.6.0 release has been the provision of access to named cell ranges and named constants (Excel: Insert/Name/Define). Juan C. Mendez provided very useful real-world sample files. 0.5.3a1 (24 May 2006) --------------------- + John Popplewell and Richard Sharp provided sample files which caused any reliance at all on DIMENSIONS records and ROW records to be abandoned. + If the file size is not a whole number of OLE sectors, a warning message is logged. Previously this caused an exception to be raised. 0.5.2 (14 March 2006) --------------------- + public release + Updated version numbers, README, HISTORY. 0.5.2a3 (13 March 2006) ----------------------- + Gnumeric writes user-defined formats with format codes starting at 50 instead of 164; worked around. + Thanks to Didrik Pinte for reporting the need for xlrd to be more tolerant of the idiosyncracies of other software, for supplying sample files, and for performing alpha testing. + '_' character in a format should be treated like an escape character; fixed. + An "empty" formula result means a zero-length string, not an empty cell! Fixed. 0.5.2a2 (9 March 2006) ---------------------- + Found that Gnumeric writes all DIMENSIONS records with nrows and ncols each 1 less than they should be (except when it clamps ncols at 256!), and pyXLwriter doesn't write ROW records. Cell memory pre- allocation was generalised to use ROW records if available with fall- back to DIMENSIONS records. 0.5.2a1 (6 March 2006) ---------------------- + pyXLwriter writes DIMENSIONS record with antique opcode 0x0000 instead of 0x0200; worked around + A file written by Gnumeric had zeroes in DIMENSIONS record but data in cell A1; worked around 0.5.1 (18 Feb 2006) -------------------- + released to Journyx + Python 2.1 mmap requires file to be opened for update access. Added fall-back to read-only access without mmap if 2.1 open fails because "permission denied". 0.5 (7 Feb 2006) ---------------- + released to Journyx + Now works with Python 2.1. Backporting to Python 2.1 was partially funded by Journyx - provider of timesheet and project accounting solutions (http://journyx.com/) + open_workbook() can be given the contents of a file instead of its name. Thanks to Remco Boerma for the suggestion. + New module attribute __VERSION__ (as a string; for example "0.5") + Minor enhancements to classification of formats as date or not-date. + Added warnings about files with inconsistent OLE compound document structures. Thanks to Roman V. Kiseliov (author of pyExcelerator) for the tip-off. 0.4a1, (7 Sept 2005) -------------------- + released to Laurent T. + Book and sheet objects can now be pickled and unpickled. Instead of reading a large spreadsheet multiple times, consider pickling it once and loading the saved pickle; can be much faster. Thanks to Laurent Thioudellet for the enhancement request. + Using the mmap module can be turned off. But you would only do that for benchmarking purposes. + Handling NUMBER records has been made faster 0.3a1 (15 May 2005) ------------------- - first public release xlrd-2.0.1/LICENSE000066400000000000000000000072731376464300000135040ustar00rootroot00000000000000There are two licenses associated with xlrd. This one relates to the bulk of the work done on the library:: Portions copyright © 2005-2009, Stephen John Machin, Lingfo Pty Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. None of the names of Stephen John Machin, Lingfo Pty Ltd and any contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This one covers some earlier work:: /*- * Copyright (c) 2001 David Giffin. * All rights reserved. * * Based on the the Java version: Andrew Khan Copyright (c) 2000. * * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by * David Giffin ." * * 4. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by * David Giffin ." * * THIS SOFTWARE IS PROVIDED BY DAVID GIFFIN ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID GIFFIN OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ xlrd-2.0.1/MANIFEST.in000066400000000000000000000000701376464300000142210ustar00rootroot00000000000000include CHANGELOG.rst include LICENSE include README.md xlrd-2.0.1/README.rst000066400000000000000000000040111376464300000141510ustar00rootroot00000000000000xlrd ==== |Build Status|_ |Coverage Status|_ |Documentation|_ |PyPI version|_ .. |Build Status| image:: https://circleci.com/gh/python-excel/xlrd/tree/master.svg?style=shield .. _Build Status: https://circleci.com/gh/python-excel/xlrd/tree/master .. |Coverage Status| image:: https://codecov.io/gh/python-excel/xlrd/branch/master/graph/badge.svg?token=lNSqwBBbvk .. _Coverage Status: https://codecov.io/gh/python-excel/xlrd .. |Documentation| image:: https://readthedocs.org/projects/xlrd/badge/?version=latest .. _Documentation: http://xlrd.readthedocs.io/en/latest/?badge=latest .. |PyPI version| image:: https://badge.fury.io/py/xlrd.svg .. _PyPI version: https://badge.fury.io/py/xlrd xlrd is a library for reading data and formatting information from Excel files in the historical ``.xls`` format. .. warning:: This library will no longer read anything other than ``.xls`` files. For alternatives that read newer file formats, please see http://www.python-excel.org/. The following are also not supported but will safely and reliably be ignored: * Charts, Macros, Pictures, any other embedded object, **including** embedded worksheets. * VBA modules * Formulas, but results of formula calculations are extracted. * Comments * Hyperlinks * Autofilters, advanced filters, pivot tables, conditional formatting, data validation Password-protected files are not supported and cannot be read by this library. Quick start: .. code-block:: python import xlrd book = xlrd.open_workbook("myfile.xls") print("The number of worksheets is {0}".format(book.nsheets)) print("Worksheet name(s): {0}".format(book.sheet_names())) sh = book.sheet_by_index(0) print("{0} {1} {2}".format(sh.name, sh.nrows, sh.ncols)) print("Cell D30 is {0}".format(sh.cell_value(rowx=29, colx=3))) for rx in range(sh.nrows): print(sh.row(rx)) From the command line, this will show the first, second and last rows of each sheet in each file: .. code-block:: bash python PYDIR/scripts/runxlrd.py 3rows *blah*.xls xlrd-2.0.1/docs/000077500000000000000000000000001376464300000134165ustar00rootroot00000000000000xlrd-2.0.1/docs/Makefile000066400000000000000000000114471376464300000150650ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xlrd-2.0.1/docs/acknowledgements.rst000066400000000000000000000032411376464300000175020ustar00rootroot00000000000000Acknowledgements ================ Many thanks to to John Machin for originally writing :mod:`xlrd` and tirelessly supporting it for many years before retiring. * This package started life as a translation from C into Python of parts of a utility called "xlreader" developed by David Giffin. "This product includes software developed by David Giffin ." * OpenOffice.org has truly excellent documentation of the Microsoft Excel file formats and Compound Document file format, authored by Daniel Rentz. See http://sc.openoffice.org * U+5F20 U+654F: over a decade of inspiration, support, and interesting decoding opportunities. * Ksenia Marasanova: sample Macintosh and non-Latin1 files, alpha testing * Backporting to Python 2.1 was partially funded by Journyx - provider of timesheet and project accounting solutions (http://journyx.com/). * Provision of formatting information in version 0.6.1 was funded by `Simplistix Ltd`__. __ http://www.simplistix.co.uk Development of this package would not have been possible without the document OpenOffice.org's Documentation of the Microsoft Excel File Format" ("OOo docs" for short). The latest version is available from OpenOffice.org in `PDF format`__ and `ODT format`__. Small portions of the OOo docs are reproduced in this document. A study of the OOo docs is recommended for those who wish a deeper understanding of the Excel file layout than the xlrd docs can provide. __ http://sc.openoffice.org/excelfileformat.pdf __ http://sc.openoffice.org/excelfileformat.odt Backporting to Python 2.1 was partially funded by `Journyx - provider of timesheet and project accounting solutions`__. __ http://journyx.com/ xlrd-2.0.1/docs/api.rst000066400000000000000000000015611376464300000147240ustar00rootroot00000000000000API Reference ============= xlrd ---- .. automodule:: xlrd :members: xlrd.biffh ---------- .. automodule:: xlrd.biffh :members: xlrd.book --------- .. automodule:: xlrd.book :members: xlrd.compdoc ------------ .. automodule:: xlrd.compdoc :members: xlrd.formatting --------------- .. automodule:: xlrd.formatting :members: xlrd.formula ------------- .. automodule:: xlrd.formula :members: xlrd.sheet ---------- .. currentmodule:: xlrd.sheet .. autoclass:: xlrd.sheet.Sheet :members: :exclude-members: gcw, col .. method:: col(colx) Returns a sequence of the :class:`Cell` objects in the given column. .. autoattribute:: xlrd.sheet.Sheet.gcw :annotation: .. automodule:: xlrd.sheet :members: :exclude-members: Sheet xlrd.xldate ----------- .. currentmodule:: xlrd.xldate .. automodule:: xlrd.xldate :members: xlrd-2.0.1/docs/changes.rst000066400000000000000000000000701376464300000155550ustar00rootroot00000000000000 .. currentmodule:: xlrd .. include:: ../CHANGELOG.rst xlrd-2.0.1/docs/conf.py000066400000000000000000000012531376464300000147160ustar00rootroot00000000000000import datetime import os from xlrd.info import __VERSION__ on_rtd = os.environ.get('READTHEDOCS', None) == 'True' intersphinx_mapping = {'http://docs.python.org': None} extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] source_suffix = '.rst' master_doc = 'index' project = u'xlrd' copyright = '2005-%s Stephen John Machin, Lingfo Pty Ltd' % datetime.datetime.now().year version = release = __VERSION__ exclude_patterns = ['_build'] pygments_style = 'sphinx' if on_rtd: html_theme = 'default' else: html_theme = 'classic' htmlhelp_basename = project+'doc' intersphinx_mapping = {'python': ('http://docs.python.org', None)} autodoc_member_order = 'bysource' xlrd-2.0.1/docs/dates.rst000066400000000000000000000076251376464300000152620ustar00rootroot00000000000000Dates in Excel spreadsheets =========================== .. currentmodule:: xlrd.xldate In reality, there are no such things. What you have are floating point numbers and pious hope. There are several problems with Excel dates: 1. Dates are not stored as a separate data type; they are stored as floating point numbers and you have to rely on: - the "number format" applied to them in Excel and/or - knowing which cells are supposed to have dates in them. This module helps with the former by inspecting the format that has been applied to each number cell; if it appears to be a date format, the cell is classified as a date rather than a number. Feedback on this feature, especially from non-English-speaking locales, would be appreciated. 2. Excel for Windows stores dates by default as the number of days (or fraction thereof) since ``1899-12-31T00:00:00``. Excel for Macintosh uses a default start date of ``1904-01-01T00:00:00``. The date system can be changed in Excel on a per-workbook basis (for example: Tools -> Options -> Calculation, tick the "1904 date system" box). This is of course a bad idea if there are already dates in the workbook. There is no good reason to change it even if there are no dates in the workbook. Which date system is in use is recorded in the workbook. A workbook transported from Windows to Macintosh (or vice versa) will work correctly with the host Excel. When using this package's :func:`xldate_as_tuple` function to convert numbers from a workbook, you must use the :attr:`~xlrd.Book.datemode` attribute of the :class:`~xlrd.Book` object. If you guess, or make a judgement depending on where you believe the workbook was created, you run the risk of being 1462 days out of kilter. Reference: https://support.microsoft.com/en-us/help/180162/xl-the-1900-date-system-vs.-the-1904-date-system 3. The Excel implementation of the Windows-default 1900-based date system works on the incorrect premise that 1900 was a leap year. It interprets the number 60 as meaning ``1900-02-29``, which is not a valid date. Consequently, any number less than 61 is ambiguous. For example, is 59 the result of ``1900-02-28`` entered directly, or is it ``1900-03-01`` minus 2 days? The OpenOffice.org Calc program "corrects" the Microsoft problem; entering ``1900-02-27`` causes the number 59 to be stored. Save as an XLS file, then open the file with Excel and you'll see ``1900-02-28`` displayed. Reference: https://support.microsoft.com/en-us/help/214326/excel-incorrectly-assumes-that-the-year-1900-is-a-leap-year 4. The Macintosh-default 1904-based date system counts ``1904-01-02`` as day 1 and ``1904-01-01`` as day zero. Thus any number such that ``(0.0 <= number < 1.0)`` is ambiguous. Is 0.625 a time of day (``15:00:00``), independent of the calendar, or should it be interpreted as an instant on a particular day (``1904-01-01T15:00:00``)? The functions in :mod:`~xlrd.xldate` take the view that such a number is a calendar-independent time of day (like Python's :class:`datetime.time` type) for both date systems. This is consistent with more recent Microsoft documentation. For example, the help file for Excel 2002, which says that the first day in the 1904 date system is ``1904-01-02``. 5. Usage of the Excel ``DATE()`` function may leave strange dates in a spreadsheet. Quoting the help file in respect of the 1900 date system:: If year is between 0 (zero) and 1899 (inclusive), Excel adds that value to 1900 to calculate the year. For example, DATE(108,1,2) returns January 2, 2008 (1900+108). This gimmick, semi-defensible only for arguments up to 99 and only in the pre-Y2K-awareness era, means that ``DATE(1899, 12, 31)`` is interpreted as ``3799-12-31``. For further information, please refer to the documentation for the functions in :mod:`~xlrd.xldate`. xlrd-2.0.1/docs/development.rst000066400000000000000000000024541376464300000164770ustar00rootroot00000000000000Development =========== .. highlight:: bash If you wish to contribute to this project, then you should fork the repository found here: https://github.com/python-excel/xlrd Once that has been done and you have a checkout, you can follow these instructions to perform various development tasks: Setting up a virtualenv ----------------------- The recommended way to set up a development environment is to turn your checkout into a virtualenv and then install the package in editable form as follows:: $ virtualenv . $ bin/pip install -e .[test] Running the tests ----------------- Once you've set up a virtualenv, the tests can be run as follows:: $ source bin/activate $ pytest Building the documentation -------------------------- The Sphinx documentation is built by doing the following, having activated the virtualenv above, from the directory containing setup.py:: $ source bin/activate $ cd docs $ make html To check that the description that will be used on PyPI renders properly, do the following:: $ python setup.py --long-description | rst2html.py > desc.html Making a release ---------------- To make a release, just update the version in ``xlrd.info.__VERSION__``, update the change log and push to https://github.com/python-excel/xlrd and Carthorse should take care of the rest. xlrd-2.0.1/docs/formatting.rst000066400000000000000000000105311376464300000163220ustar00rootroot00000000000000Formatting information in Excel Spreadsheets ============================================ Introduction ------------ This collection of features, new in xlrd version 0.6.1, is intended to provide the information needed to: - display/render spreadsheet contents (say) on a screen or in a PDF file - copy spreadsheet data to another file without losing the ability to display/render it. .. _palette: The Palette; Colour Indexes --------------------------- A colour is represented in Excel as a ``(red, green, blue)`` ("RGB") tuple with each component in ``range(256)``. However it is not possible to access an unlimited number of colours; each spreadsheet is limited to a palette of 64 different colours (24 in Excel 3.0 and 4.0, 8 in Excel 2.0). Colours are referenced by an index ("colour index") into this palette. Colour indexes 0 to 7 represent 8 fixed built-in colours: black, white, red, green, blue, yellow, magenta, and cyan. The remaining colours in the palette (8 to 63 in Excel 5.0 and later) can be changed by the user. In the Excel 2003 UI, Tools -> Options -> Color presents a palette of 7 rows of 8 colours. The last two rows are reserved for use in charts. The correspondence between this grid and the assigned colour indexes is NOT left-to-right top-to-bottom. Indexes 8 to 15 correspond to changeable parallels of the 8 fixed colours -- for example, index 7 is forever cyan; index 15 starts off being cyan but can be changed by the user. The default colour for each index depends on the file version; tables of the defaults are available in the source code. If the user changes one or more colours, a ``PALETTE`` record appears in the XLS file -- it gives the RGB values for *all* changeable indexes. Note that colours can be used in "number formats": ``[CYAN]....`` and ``[COLOR8]....`` refer to colour index 7; ``[COLOR16]....`` will produce cyan unless the user changes colour index 15 to something else. In addition, there are several "magic" colour indexes used by Excel: ``0x18`` (BIFF3-BIFF4), ``0x40`` (BIFF5-BIFF8): System window text colour for border lines (used in ``XF``, ``CF``, and ``WINDOW2`` records) ``0x19`` (BIFF3-BIFF4), ``0x41`` (BIFF5-BIFF8): System window background colour for pattern background (used in ``XF`` and ``CF`` records ) ``0x43``: System face colour (dialogue background colour) ``0x4D``: System window text colour for chart border lines ``0x4E``: System window background colour for chart areas ``0x4F``: Automatic colour for chart border lines (seems to be always Black) ``0x50``: System ToolTip background colour (used in note objects) ``0x51``: System ToolTip text colour (used in note objects) ``0x7FFF``: System window text colour for fonts (used in ``FONT`` and ``CF`` records). .. note:: ``0x7FFF`` appears to be the *default* colour index. It appears quite often in ``FONT`` records. Default Formatting ------------------ Default formatting is applied to all empty cells (those not described by a cell record): - Firstly, row default information (``ROW`` record, :class:`~xlrd.sheet.Rowinfo` class) is used if available. - Failing that, column default information (``COLINFO`` record, :class:`~xlrd.sheet.Colinfo` class) is used if available. - As a last resort the worksheet/workbook default cell format will be used; this should always be present in an Excel file, described by the ``XF`` record with the fixed index 15 (0-based). By default, it uses the worksheet/workbook default cell style, described by the very first ``XF`` record (index 0). Formatting features not included in xlrd ---------------------------------------- - Asian phonetic text (known as "ruby"), used for Japanese furigana. See OOo docs s3.4.2 (p15) - Conditional formatting. See OOo docs s5.12, s6.21 (CONDFMT record), s6.16 (CF record) - Miscellaneous sheet-level and book-level items, e.g. printing layout, screen panes. - Modern Excel file versions don't keep most of the built-in "number formats" in the file; Excel loads formats according to the user's locale. Currently, xlrd's emulation of this is limited to a hard-wired table that applies to the US English locale. This may mean that currency symbols, date order, thousands separator, decimals separator, etc are inappropriate. .. note:: This does not affect users who are copying XLS files, only those who are visually rendering cells. xlrd-2.0.1/docs/index.rst000066400000000000000000000011011376464300000152500ustar00rootroot00000000000000.. include:: ../README.rst You may also wish to consult the `tutorial`__. __ https://github.com/python-excel/tutorial Details: .. toctree:: :maxdepth: 1 unicode.rst dates.rst references.rst formatting.rst on_demand.rst api.rst For details of how to get involved in development of this package, and other meta-information, please see the sections below: .. toctree:: :maxdepth: 1 development.rst changes.rst acknowledgements.rst licenses.rst Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` xlrd-2.0.1/docs/licenses.rst000066400000000000000000000000621376464300000157530ustar00rootroot00000000000000Licenses ======== .. literalinclude:: ../LICENSE xlrd-2.0.1/docs/make.bat000066400000000000000000000111231376464300000150210ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end xlrd-2.0.1/docs/on_demand.rst000066400000000000000000000047221376464300000161010ustar00rootroot00000000000000Loading worksheets on demand ============================= .. currentmodule:: xlrd.book This feature, new in version 0.7.1, is governed by the ``on_demand`` argument to the :func:`~xlrd.open_workbook` function and allows saving memory and time by loading only those sheets that the caller is interested in, and releasing sheets when no longer required. ``on_demand=False`` (default): No change. :func:`~xlrd.open_workbook` loads global data and all sheets, releases resources no longer required (principally the :class:`str` or :class:`mmap.mmap` object containing the Workbook stream), and returns. ``on_demand=True`` and BIFF version < 5.0: A warning message is emitted, ``on_demand`` is recorded as ``False``, and the old process is followed. ``on_demand=True`` and BIFF version >= 5.0: :func:`~xlrd.open_workbook` loads global data and returns without releasing resources. At this stage, the only information available about sheets is :attr:`Book.nsheets` and :meth:`Book.sheet_names`. :meth:`Book.sheet_by_name` and :meth:`Book.sheet_by_index` will load the requested sheet if it is not already loaded. :meth:`Book.sheets` will load all unloaded sheets. The caller may save memory by calling :meth:`Book.unload_sheet` when finished with the sheet. This applies irrespective of the state of ``on_demand``. The caller may re-load an unloaded sheet by calling :meth:`Book.sheet_by_name` or :meth:`Book.sheet_by_index`, except if the required resources have been released (which will have happened automatically when ``on_demand`` is false). This is the only case where an exception will be raised. The caller may query the state of a sheet using :meth:`Book.sheet_loaded`. :meth:`Book.release_resources` may used to save memory and close any memory-mapped file before proceeding to examine already-loaded sheets. Once resources are released, no further sheets can be loaded. When using on-demand, it is advisable to ensure that :meth:`Book.release_resources` is always called, even if an exception is raised in your own code; otherwise if the input file has been memory-mapped, the :class:`mmap.mmap` object will not be closed and you will not be able to access the physical file until your Python process terminates. This can be done by calling :meth:`Book.release_resources` explicitly in the finally part of a try/finally block. The Book object is also a context manager, so you can wrap your code in a ``with`` statement that will make sure underlying resources are closed. xlrd-2.0.1/docs/references.rst000066400000000000000000000034471376464300000163010ustar00rootroot00000000000000Named references, constants, formulas, and macros ================================================= .. currentmodule:: xlrd.book A name is used to refer to a cell, a group of cells, a constant value, a formula, or a macro. Usually the scope of a name is global across the whole workbook. However it can be local to a worksheet. For example, if the sales figures are in different cells in different sheets, the user may define the name "Sales" in each sheet. There are built-in names, like "Print_Area" and "Print_Titles"; these two are naturally local to a sheet. To inspect the names with a user interface like MS Excel, OOo Calc, or Gnumeric, click on Insert -> Names -> Define. This will show the global names, plus those local to the currently selected sheet. A :class:`Book` object provides two dictionaries (:attr:`Book.name_map` and :attr:`Book.name_and_scope_map`) and a list (:attr:`Book.name_obj_list`) which allow various ways of accessing the :class:`Name` objects. There is one :class:`Name` object for each `NAME` record found in the workbook. :class:`Name` objects have many attributes, several of which are relevant only when ``obj.macro`` is ``1``. In the examples directory you will find ``namesdemo.xls`` which showcases the many different ways that names can be used, and ``xlrdnamesAPIdemo.py`` which offers 3 different queries for inspecting the names in your files, and shows how to extract whatever a name is referring to. There is currently one "convenience method", :meth:`Name.cell`, which extracts the value in the case where the name refers to a single cell. The source code for :meth:`Name.cell` is an extra source of information on how the :class:`Name` attributes hang together. .. note:: Name information is *not* extracted from files older than Excel 5.0 (``Book.biff_version < 50``). xlrd-2.0.1/docs/unicode.rst000066400000000000000000000026541376464300000156050ustar00rootroot00000000000000Handling of Unicode =================== This package presents all text strings as Python unicode objects. From Excel 97 onwards, text in Excel spreadsheets has been stored as `UTF-16LE `_ (a 16-bit Unicode Transformation Format). Older files (Excel 95 and earlier) don't keep strings in Unicode; a ``CODEPAGE`` record provides a codepage number (for example, 1252) which is used by xlrd to derive the encoding (for same example: "cp1252") which is used to translate to Unicode. If the ``CODEPAGE`` record is missing (possible if the file was created by third-party software), ``xlrd`` will assume that the encoding is ascii, and keep going. If the actual encoding is not ascii, a :class:`UnicodeDecodeError` exception will be raised and you will need to determine the encoding yourself, and tell xlrd: .. code-block:: python book = xlrd.open_workbook(..., encoding_override="cp1252") If the ``CODEPAGE`` record exists but is wrong (for example, the codepage number is 1251, but the strings are actually encoded in koi8_r), it can be overridden using the same mechanism. The supplied ``runxlrd.py`` has a corresponding command-line argument, which may be used for experimentation: .. code-block:: bash runxlrd.py -e koi8_r 3rows myfile.xls The first place to look for an encoding, the "codec name", is `the Python documentation`__. __ https://docs.python.org/library/codecs.html#standard-encodings xlrd-2.0.1/scripts/000077500000000000000000000000001376464300000141555ustar00rootroot00000000000000xlrd-2.0.1/scripts/runxlrd.py000066400000000000000000000373111376464300000162320ustar00rootroot00000000000000#!/usr/bin/env python # Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd # This script is part of the xlrd package, which is released under a # BSD-style licence. from __future__ import print_function cmd_doc = """ Commands: 2rows Print the contents of first and last row in each sheet 3rows Print the contents of first, second and last row in each sheet bench Same as "show", but doesn't print -- for profiling biff_count[1] Print a count of each type of BIFF record in the file biff_dump[1] Print a dump (char and hex) of the BIFF records in the file fonts hdr + print a dump of all font objects hdr Mini-overview of file (no per-sheet information) hotshot Do a hotshot profile run e.g. ... -f1 hotshot bench bigfile*.xls labels Dump of sheet.col_label_ranges and ...row... for each sheet name_dump Dump of each object in book.name_obj_list names Print brief information for each NAME record ov Overview of file profile Like "hotshot", but uses cProfile show Print the contents of all rows in each sheet version[0] Print versions of xlrd and Python and exit xfc Print "XF counts" and cell-type counts -- see code for details [0] means no file arg [1] means only one file arg i.e. no glob.glob pattern """ options = None if __name__ == "__main__": import xlrd import sys import time import glob import traceback import gc from xlrd.timemachine import xrange, REPR class LogHandler(object): def __init__(self, logfileobj): self.logfileobj = logfileobj self.fileheading = None self.shown = 0 def setfileheading(self, fileheading): self.fileheading = fileheading self.shown = 0 def write(self, text): if self.fileheading and not self.shown: self.logfileobj.write(self.fileheading) self.shown = 1 self.logfileobj.write(text) null_cell = xlrd.empty_cell def show_row(bk, sh, rowx, colrange, printit): if bk.ragged_rows: colrange = range(sh.row_len(rowx)) if not colrange: return if printit: print() if bk.formatting_info: for colx, ty, val, cxfx in get_row_data(bk, sh, rowx, colrange): if printit: print("cell %s%d: type=%d, data: %r, xfx: %s" % (xlrd.colname(colx), rowx+1, ty, val, cxfx)) else: for colx, ty, val, _unused in get_row_data(bk, sh, rowx, colrange): if printit: print("cell %s%d: type=%d, data: %r" % (xlrd.colname(colx), rowx+1, ty, val)) def get_row_data(bk, sh, rowx, colrange): result = [] dmode = bk.datemode ctys = sh.row_types(rowx) cvals = sh.row_values(rowx) for colx in colrange: cty = ctys[colx] cval = cvals[colx] if bk.formatting_info: cxfx = str(sh.cell_xf_index(rowx, colx)) else: cxfx = '' if cty == xlrd.XL_CELL_DATE: try: showval = xlrd.xldate_as_tuple(cval, dmode) except xlrd.XLDateError as e: showval = "%s:%s" % (type(e).__name__, e) cty = xlrd.XL_CELL_ERROR elif cty == xlrd.XL_CELL_ERROR: showval = xlrd.error_text_from_code.get(cval, '' % cval) else: showval = cval result.append((colx, cty, showval, cxfx)) return result def bk_header(bk): print() print("BIFF version: %s; datemode: %s" % (xlrd.biff_text_from_num[bk.biff_version], bk.datemode)) print("codepage: %r (encoding: %s); countries: %r" % (bk.codepage, bk.encoding, bk.countries)) print("Last saved by: %r" % bk.user_name) print("Number of data sheets: %d" % bk.nsheets) print("Use mmap: %d; Formatting: %d; On demand: %d" % (bk.use_mmap, bk.formatting_info, bk.on_demand)) print("Ragged rows: %d" % bk.ragged_rows) if bk.formatting_info: print("FORMATs: %d, FONTs: %d, XFs: %d" % (len(bk.format_list), len(bk.font_list), len(bk.xf_list))) if not options.suppress_timing: print("Load time: %.2f seconds (stage 1) %.2f seconds (stage 2)" % (bk.load_time_stage_1, bk.load_time_stage_2)) print() def show_fonts(bk): print("Fonts:") for x in xrange(len(bk.font_list)): font = bk.font_list[x] font.dump(header='== Index %d ==' % x, indent=4) def show_names(bk, dump=0): bk_header(bk) if bk.biff_version < 50: print("Names not extracted in this BIFF version") return nlist = bk.name_obj_list print("Name list: %d entries" % len(nlist)) for nobj in nlist: if dump: nobj.dump(sys.stdout, header="\n=== Dump of name_obj_list[%d] ===" % nobj.name_index) else: print("[%d]\tName:%r macro:%r scope:%d\n\tresult:%r\n" % (nobj.name_index, nobj.name, nobj.macro, nobj.scope, nobj.result)) def print_labels(sh, labs, title): if not labs:return for rlo, rhi, clo, chi in labs: print("%s label range %s:%s contains:" % (title, xlrd.cellname(rlo, clo), xlrd.cellname(rhi-1, chi-1))) for rx in xrange(rlo, rhi): for cx in xrange(clo, chi): print(" %s: %r" % (xlrd.cellname(rx, cx), sh.cell_value(rx, cx))) def show_labels(bk): # bk_header(bk) hdr = 0 for shx in range(bk.nsheets): sh = bk.sheet_by_index(shx) clabs = sh.col_label_ranges rlabs = sh.row_label_ranges if clabs or rlabs: if not hdr: bk_header(bk) hdr = 1 print("sheet %d: name = %r; nrows = %d; ncols = %d" % (shx, sh.name, sh.nrows, sh.ncols)) print_labels(sh, clabs, 'Col') print_labels(sh, rlabs, 'Row') if bk.on_demand: bk.unload_sheet(shx) def show(bk, nshow=65535, printit=1): bk_header(bk) if 0: rclist = xlrd.sheet.rc_stats.items() rclist = sorted(rclist) print("rc stats") for k, v in rclist: print("0x%04x %7d" % (k, v)) if options.onesheet: try: shx = int(options.onesheet) except ValueError: shx = bk.sheet_by_name(options.onesheet).number shxrange = [shx] else: shxrange = range(bk.nsheets) # print("shxrange", list(shxrange)) for shx in shxrange: sh = bk.sheet_by_index(shx) nrows, ncols = sh.nrows, sh.ncols colrange = range(ncols) anshow = min(nshow, nrows) print("sheet %d: name = %s; nrows = %d; ncols = %d" % (shx, REPR(sh.name), sh.nrows, sh.ncols)) if nrows and ncols: # Beat the bounds for rowx in xrange(nrows): nc = sh.row_len(rowx) if nc: sh.row_types(rowx)[nc-1] sh.row_values(rowx)[nc-1] sh.cell(rowx, nc-1) for rowx in xrange(anshow-1): if not printit and rowx % 10000 == 1 and rowx > 1: print("done %d rows" % (rowx-1,)) show_row(bk, sh, rowx, colrange, printit) if anshow and nrows: show_row(bk, sh, nrows-1, colrange, printit) print() if bk.on_demand: bk.unload_sheet(shx) def count_xfs(bk): bk_header(bk) for shx in range(bk.nsheets): sh = bk.sheet_by_index(shx) nrows = sh.nrows print("sheet %d: name = %r; nrows = %d; ncols = %d" % (shx, sh.name, sh.nrows, sh.ncols)) # Access all xfindexes to force gathering stats type_stats = [0, 0, 0, 0, 0, 0, 0] for rowx in xrange(nrows): for colx in xrange(sh.row_len(rowx)): xfx = sh.cell_xf_index(rowx, colx) assert xfx >= 0 cty = sh.cell_type(rowx, colx) type_stats[cty] += 1 print("XF stats", sh._xf_index_stats) print("type stats", type_stats) print() if bk.on_demand: bk.unload_sheet(shx) def main(cmd_args): import optparse global options usage = "\n%prog [options] command [input-file-patterns]\n" + cmd_doc oparser = optparse.OptionParser(usage) oparser.add_option( "-l", "--logfilename", default="", help="contains error messages") oparser.add_option( "-v", "--verbosity", type="int", default=0, help="level of information and diagnostics provided") oparser.add_option( "-m", "--mmap", type="int", default=-1, help="1: use mmap; 0: don't use mmap; -1: accept heuristic") oparser.add_option( "-e", "--encoding", default="", help="encoding override") oparser.add_option( "-f", "--formatting", type="int", default=0, help="0 (default): no fmt info\n" "1: fmt info (all cells)\n", ) oparser.add_option( "-g", "--gc", type="int", default=0, help="0: auto gc enabled; 1: auto gc disabled, manual collect after each file; 2: no gc") oparser.add_option( "-s", "--onesheet", default="", help="restrict output to this sheet (name or index)") oparser.add_option( "-u", "--unnumbered", action="store_true", default=0, help="omit line numbers or offsets in biff_dump") oparser.add_option( "-d", "--on-demand", action="store_true", default=0, help="load sheets on demand instead of all at once") oparser.add_option( "-t", "--suppress-timing", action="store_true", default=0, help="don't print timings (diffs are less messy)") oparser.add_option( "-r", "--ragged-rows", action="store_true", default=0, help="open_workbook(..., ragged_rows=True)") options, args = oparser.parse_args(cmd_args) if len(args) == 1 and args[0] in ("version", ): pass elif len(args) < 2: oparser.error("Expected at least 2 args, found %d" % len(args)) cmd = args[0] xlrd_version = getattr(xlrd, "__VERSION__", "unknown; before 0.5") if cmd == 'biff_dump': xlrd.dump(args[1], unnumbered=options.unnumbered) sys.exit(0) if cmd == 'biff_count': xlrd.count_records(args[1]) sys.exit(0) if cmd == 'version': print("xlrd: %s, from %s" % (xlrd_version, xlrd.__file__)) print("Python:", sys.version) sys.exit(0) if options.logfilename: logfile = LogHandler(open(options.logfilename, 'w')) else: logfile = sys.stdout mmap_opt = options.mmap mmap_arg = xlrd.USE_MMAP if mmap_opt in (1, 0): mmap_arg = mmap_opt elif mmap_opt != -1: print('Unexpected value (%r) for mmap option -- assuming default' % mmap_opt) fmt_opt = options.formatting | (cmd in ('xfc', )) gc_mode = options.gc if gc_mode: gc.disable() for pattern in args[1:]: for fname in glob.glob(pattern): print("\n=== File: %s ===" % fname) if logfile != sys.stdout: logfile.setfileheading("\n=== File: %s ===\n" % fname) if gc_mode == 1: n_unreachable = gc.collect() if n_unreachable: print("GC before open:", n_unreachable, "unreachable objects") try: t0 = time.time() bk = xlrd.open_workbook( fname, verbosity=options.verbosity, logfile=logfile, use_mmap=mmap_arg, encoding_override=options.encoding, formatting_info=fmt_opt, on_demand=options.on_demand, ragged_rows=options.ragged_rows, ) t1 = time.time() if not options.suppress_timing: print("Open took %.2f seconds" % (t1-t0,)) except xlrd.XLRDError as e: print("*** Open failed: %s: %s" % (type(e).__name__, e)) continue except KeyboardInterrupt: print("*** KeyboardInterrupt ***") traceback.print_exc(file=sys.stdout) sys.exit(1) except BaseException as e: print("*** Open failed: %s: %s" % (type(e).__name__, e)) traceback.print_exc(file=sys.stdout) continue t0 = time.time() if cmd == 'hdr': bk_header(bk) elif cmd == 'ov': # OverView show(bk, 0) elif cmd == 'show': # all rows show(bk) elif cmd == '2rows': # first row and last row show(bk, 2) elif cmd == '3rows': # first row, 2nd row and last row show(bk, 3) elif cmd == 'bench': show(bk, printit=0) elif cmd == 'fonts': bk_header(bk) show_fonts(bk) elif cmd == 'names': # named reference list show_names(bk) elif cmd == 'name_dump': # named reference list show_names(bk, dump=1) elif cmd == 'labels': show_labels(bk) elif cmd == 'xfc': count_xfs(bk) else: print("*** Unknown command <%s>" % cmd) sys.exit(1) del bk if gc_mode == 1: n_unreachable = gc.collect() if n_unreachable: print("GC post cmd:", fname, "->", n_unreachable, "unreachable objects") if not options.suppress_timing: t1 = time.time() print("\ncommand took %.2f seconds\n" % (t1-t0,)) return None av = sys.argv[1:] if not av: main(av) firstarg = av[0].lower() if firstarg == "hotshot": import hotshot import hotshot.stats av = av[1:] prof_log_name = "XXXX.prof" prof = hotshot.Profile(prof_log_name) # benchtime, result = prof.runcall(main, *av) result = prof.runcall(main, *(av, )) print("result", repr(result)) prof.close() stats = hotshot.stats.load(prof_log_name) stats.strip_dirs() stats.sort_stats('time', 'calls') stats.print_stats(20) elif firstarg == "profile": import cProfile av = av[1:] cProfile.run('main(av)', 'YYYY.prof') import pstats p = pstats.Stats('YYYY.prof') p.strip_dirs().sort_stats('cumulative').print_stats(30) else: main(av) xlrd-2.0.1/setup.cfg000066400000000000000000000000771376464300000143130ustar00rootroot00000000000000[bdist_wheel] universal = 1 [metadata] license_file = LICENSE xlrd-2.0.1/setup.py000066400000000000000000000027371376464300000142110ustar00rootroot00000000000000from setuptools import setup from xlrd.info import __VERSION__ setup( name='xlrd', version=__VERSION__, author='Chris Withers', author_email='chris@withers.org', url='http://www.python-excel.org/', packages=['xlrd'], scripts=[ 'scripts/runxlrd.py', ], description=( 'Library for developers to extract data from ' 'Microsoft Excel (tm) .xls spreadsheet files' ), long_description=open('README.rst').read(), license='BSD', keywords=['xls', 'excel', 'spreadsheet', 'workbook'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Operating System :: OS Independent', 'Topic :: Database', 'Topic :: Office/Business', 'Topic :: Software Development :: Libraries :: Python Modules', ], python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*", extras_require=dict( test=['pytest', 'pytest-cov'], docs=['sphinx'], build=['wheel', 'twine'] ) ) xlrd-2.0.1/tests/000077500000000000000000000000001376464300000136305ustar00rootroot00000000000000xlrd-2.0.1/tests/__init__.py000066400000000000000000000000001376464300000157270ustar00rootroot00000000000000xlrd-2.0.1/tests/helpers.py000066400000000000000000000002001376464300000156340ustar00rootroot00000000000000import os def from_sample(filename): return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'samples', filename) xlrd-2.0.1/tests/samples/000077500000000000000000000000001376464300000152745ustar00rootroot00000000000000xlrd-2.0.1/tests/samples/Formate.xls000066400000000000000000000250001376464300000174160ustar00rootroot00000000000000ࡱ;  Root Entry  \pCalc Ba==@ 8X@"1Calibri1Arial1Arial1Arial1Calibri1 Calibri1Calibri1Calibri GENERAL DD/MM/YYYY 0.000000DDDD", "MMMM\ DD", "YYYY HH:MMH:MM:SS\ AM/PM0% 0.0%y:_-* #,##0.00" "_-;\-* #,##0.00" "_-;_-* \-??" "_-;_-@_-L_-* #,##0.00\ [$ -407]_-;\-* #,##0.00\ [$ -407]_-;_-* \-??\ [$ -407]_-;_-@_-                + )  *           A ! !  8  H    H    H   `Bltt1 ܅Bltt3[Formate11Trj03  @@  HuberckerckerMorgenMittagAbendsgutschlechtvielwenig MERGED CELLSREDGREENBLUE R cc   dMbP?_%,*+&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?U }  ,,,,,,,,, , ~ * ~ X ~ * -؂-؂? >>? 5F? ^I +? X9v? ~   ~ PH0(  >@  gg   dMbP?_%,*+&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?U ,,,,,~ r       PH 0(  >@gg   dMbP?_%,*+&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?U  ,,,,,,,,, , , ,~ ~ "~ ~ B~ ~ b ~  ~  ~ ~ ~ 2~ 0 (   p  6NMM?&]`  "d,, ` `? ` `?3l323  NM4  3QQ ; QQ3 ff6 ffffMNd4E4DFA33 &! 43*&! ! 4523 M NM43 3% 3&423 M NM4444% M3'44 >@gg   dMbP?_%,*+&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?U ,,,   ! " # $ PH@0(  >@gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0HP` x manfredManfred Moitzi1@@@|ҳ@Փ՜.+,D՜.+,\Root EntryF@WorkbookmCompObjIOle SummaryInformation(DocumentSummaryInformation8txlrd-2.0.1/tests/samples/biff4_no_format_no_window2.xls000066400000000000000000000053721376464300000232360ustar00rootroot00000000000000 6 ddd1 Arial1 Arial1 Arial1 Arial1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans Serif1 MS Sans SerifC C  C  C  C  C C C C C C C C C C C  C ! C  C  C  C  C #xAC "xAqqqqC !xC "xC "xC "xAqqqC !xC !xC !xAC !xAC !xAC !xA} } } }   ID TOTAL MAIN SUB ADOG & CAT PRODUCTSDog & Cat Product  B BIRD PRODUCTS Bird Product  CSMALL ANIMAL PRODUCTSSmall Animal Product  D GIFT VOUCHER  E  F FISH PRODUCTS Fish Product  GGROOMINGGooming  H  I     J  REMAINDERED Remaindered Stock   K  PET KITTEN  Pet Kitten   L PET SMALL ANIMAL Pet Small Animal   M  MICROCHIPPING  Microchipping   NPET FISHPet Fish  OPET BIRDPet Bird  P PET PUPPY Pet Puppy  Q  R  SPET SITTING/BOARDINGPet Sitting/Boarding  T TANK RESCUETraining - Dog fees  U  VFOOD DOG & CATDog & Cat Food  W FOOD BIRDS Bird Food  XFOOD SMALL ANIMALSmall Animal Food  YTREATS FOR ALLTreats For All  ZGIFTWAREGiftware xlrd-2.0.1/tests/samples/corrupted_error.xls000066400000000000000000035540001376464300000212530ustar00rootroot00000000000000ࡱ; [\]^_`abcdefghijOh+'0@Hh Untitled SpreadsheetUnknown CreatorUnknown Creator@@՜.+,0HP X`hp x   Worksheet Feuilles de calcul  B=%r8X"1Arial1.Times New Roman1.Times New Romanmm-dd-yy                  " " 83ffff̙̙3f3fff3f3f33333f33333 "@C1K T "@C1K M;.A2(DT"@C1K ?@>D8;L=K5#P( \:!"  ""lt  #, "# & !g 8  +  !-!" 05-06-2019" 08<5=>20=85!";8=0!B0;LAB0B>: &5=0 70 B>==C "@C10 23? 4C 15E2,5 4C 15 E 2,8 4C 20 E 2,8 4C 20 E 3,2 4C 25 E 2,8 4C 25 E 3,2 4C 32 E 2,8 4C 32 E 3,2 4C 40 E 3,5 4C 50 E 3,0 4C 50 E 3,5 "@C10 M;/A251 E 1,508?A57 E 3,057 E 3,576E3,0  76 E 3,5 89 E 3,089 E 3,589 E 4,0102E3,0 102 E 3,5 108 E 3,0 108 E 3,5  108 E 4,0 114 E 3,5 114 E 4,0 127 E 3,5133E3,0 133 E 4,5 159 E 4,0 159 E 4,5 159 E 5,0 219 E 4,0 219 E 5,0 219 E 6,0 426 E 6,0 10E10E1,03,10 15E15E1,5 20E10E1,5 20E20E1,2 20E20E1,5 20E20E2,0 25E25E1,5 25E25E2,0 30E15E1,5 30E20E1,5 30E20E2,0 30E30E1,5 30E30E2,0 40E20E1,5 40E20E2,0 40E25E1,5 40E25E2,0 40E40E1,5 40E40E2,0 40E40E3,0 40E40E3,5 50E25E1,5 50E25E2,0 50E50E1,5 50E50E2,0 50E50E3,0 50E50E4,0 60E30E1,5 60E30E2,0 60E30E3,0 60E40E1,5 60E40E2,0 60E40E3,0 60E40E4,0 60E60E2,0 60E60E3,0 60E60E4,0 60E60E5,0 80E40E2,0 80E40E3,0 80E60E3,0 80E80E3,0 80E80E4,0 100E50E3,0 100E50E4,0 120E60E3,0 120E80E3,0 100E100E3,0 100E100E4,0 120E120E3,0 120E120E4,0 120E120E6,0 140E140E4,0 140E140E5,0 150E150E4,0 150E150E5,0 160E120E5,0 180E180E6,0 180E180E8,0 200E200E6,0 200E200E8,0#3>;>:25E25E325E25E432E32E332E32E435E35E440E40E445E45E450E50E450E50E563E63E563E63E675E75E575E75E690E90E7 100E100E7 125E125E8(25;;5@( 6,5 ( 8 #( 8( 10 ( 12 #( 12 ( 12( 14( 16#( 188AB 1,0E1,25E2,53: 1,2E1,25E2,5 1,5E1,25E2,53?AE: 2,0E1,25E2,5 2,5E1,25E2,5 3,0E1,25E2,5 5,0E1,5E6,0204@0B 204@0B 10 204@0B 12 204@0B 14@C3@C3 6,5@C3 8@C3 10@C3 12@C3 14@C3 16@C3 18@<0BC@063353A83103500!12314 316 320 3>;>A020E4 3/:103-20063A?25E4 3/:30E4 3/:40E4 3/:50E5 3/:  *+7&C(0?:0 =0H53> ?@09A-;8AB0C &L&B"@C1K &R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @            |@ @$@\(\??@   |@ @$@/$@@   |@ @$@(\?@   |@@$@d;O@@   |@@$@/$?@    |@  @ $@ +? E@   |@  @ $@ tV+@ E@   |@ @ $@ ʡE? @   |@ #@ $@ Q^&@ E@   |@ #@ $@ jtX#@ E@  |@@$@ʡE?%@  |@#@$@\(@%@  |@#@$@)\(@%@       !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB0G"&L&B"@C1K M;.A2&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @           @@ /$?@  @#@$@~jt?@  @(@$@Cl@+@  @(@$@J +?+@  @(@$@K@+@   @ (@ $@ ˡE? +@   @ (@ $@ 㥛 @ +@   @ 333333'@ $@ jt? @   @ (@ $@ 5^I  @ +@   @ 333333'@ $@ &1@ +@  @(@$@HzG?+@  @333333'@$@{Gz?+@  @333333'@$@~jt@+@  @(@$@5^I  @@   @333333'@$@Cl?+@  !@333333'@$@+?+@  "@333333'@$@~jt@+@  "@(@$@S㥻?+@  #@333333'@$@#~j?@  #@(@$@K?+@  $@333333'@$@Q@+@  %@333333'@$@T㥛 ?+@  &@333333'@$@X9v@+@  '@(@$@/$?+@  (@333333'@$@!rh?+@  (@(@$@y&1?+@  )@333333'@$@o"@+@  *@333333'@$@K7@+@  + @ 333333'@ $@ "~j@ +@ ! !+!@!(@!$@!K7A@!+@ " ","@"333333'@"$@"NbX9@"@ # #-#@#333333'@#$@#Mb$@#@ $ $-$@$(@$$@$$C?$@ % %.%@%(@%$@%v/@%@ & &.&@&'@&$@&ʡ@&@ ' '/'@'333333'@'$@'/$@'@((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB0Q'&L&B"@C1K ?@>D8;L=K5&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @          0 1@@ 1Mbp?@ 2 1@@ 1MbX9@j@ 3 1@@ 1Cl?@@ 4 1@@ 1 rh@@ 5 1@@ 1L7A`@,@ 6 1 @ @ 1 Mb@ @ 7 1 @ @ 1 X9v? 2@ 8 1 @ @ 1 v? @ 9 1 @ @ 1 n? @ : 1 @ @ 1 bX9@ @ ; 1@@ 1333333@@ < 1@@ 1On @@ = 1@@ 1E?p@ > 1@@ 1\(\@@ ? 1@@ 1Cl@p@ @ 1@@ 1-?@ @ 1@@ 1K7@@ A 1@@ 1Mb?@ B 1@@ 1 rh?@ C 1@@ 1Mb@@ D 1@@ 1~jt?@ D 1@@ 1T㥛 ?@ E 1@@ 1J +@@ F 1@@ 1Mb?%@ G 1@@ 1Pn@@ H 1@@ 1^I +?W@ I 1@@ 19v@W@ J 1@@ 1?5^I ?@ K 1 @ @ 1 p= ף? @ !L !1!@!@ !1!v?!@ "M "1"@"@ "1" r?"@ #N #1#@#@ #1#Q?#@ $O $1$@$@ $1$ˡE?$@ %O %1%@%@ %1%V-?%@ &P &1&@&@ &1&333333@&p@ 'Q '1'@'@ '1'V-@'@ (R (1(@(@ (1()\(?(@ )S )1)@)@ )1)V- @)@ *T *1*@*(@ *1*A`"@*i@ +U +1+@+(@ +1+ˡE?+@ ,V ,1,@,@ ,1,㥛 ?,@ -W -1-@-@ -1-n?-@ .X .1.@.(@ .1.y&1?.@ /Y /1/@/(@ /1/K?/@ 0Z 010@0(@ 010/$@0@ 1[ 111@1(@ 111d;O@1@ 2\ 212@2(@ 212#~j @2@ 3] 313@3(@ 313?5^I ?3@ 4^ 414@4(@ 414jt?4@ 5_ 515@5(@ 515333333?5@ 6` 616@6(@ 616Cl?6i@ 7a 717@7(@ 7175^I ?7i@ 8a 818@8(@ 818Q?8i@ 9a 919@9(@ 919n?9i@ :b :1:@:(@ :1:V-?:@ ;c ;1;@;(@ ;1;&1?;i@ <d <1<@<(@ <1<?<@ =e =1=@=(@ =1=On@=+@ >f >1>@@>(@ >1>Zd;?>+@ ?g ?1?@@?(@ ?1?S??E@ @h @1@@@@(@ @1@w/?@E@ Ai A1A@@A(@ A1Am?A@ Bj B1B@@B(@ B1B{Gz?B@ Cj C1C@@C(@ C1C@C@ Dk D1D@@D(@ D1DZd;?D@ El E1E@@E(@ E1E/$?E,@ Fm F1F@@F(@ F1FZd;?F,@GGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB0=&L&B#&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @          n o@@@I +?@ n o@@@?@ n p@(@@'1Z?Q@ n p@@@w/?Q@ n q@@@S㥛?@ n r @ @ @ L7A`? @ n r @ (@ @ ףp= ? @ n s @ (@ @ HzG? @ n t @ (@ @ ? @ n t @ (@ @ Pn? @ n u@(@@Cl?@ n u@(@@?@ n v@(@@/$?@ n w@(@@Mb@Q@ n x@(@@L7A`?Q@ n y@(@@jt?Q@ n z@(@@\(\?@ n {@(@@J +?Q@ n |@(@@ rh?Q@ n }@(@@Q?Q@ n }@(@@rh|?Q@ n ~@(@@?@       !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB0?&L&B( &R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @           @(@@V-?3@  @(@@S?3@  @(@@p= ף?3@  @(@@x?3@  @(@@S㥛?3@   @ (@ @ = ףp=@ 3@   @ (@ @ (\? 3@   @ (@ @ K7A`@ @   @ (@ @ ? 3@   @ (@ @ Fx? 3@       !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB09&L&B!"&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @           o@  ?@  o@  HzG?@  o@  ~jt?@  o@  X9v?K@  o@  Zd;O@@   o@   -? @   o@   tV? +@   o@   J +? W@                     !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB09&L&B!"&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @           >@@ -?d@  >@@ Gz?@  >@@ l?%@                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB09&L&B!"&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @           @@ S?@  @@ X9v?@  @@ Gz?@  @(@ Mb?@  @(@ K7A`?=@   @ (@  V-? c@   @ (@  333333? @   @ @  1Zd? @   @ @  $C? @   @ (@  NbX9@ @  @(@ Cl@7@  @(@ Q?@  @(@ ?@  @(@ S㥛?@  @(@ 333333?{@       !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg  *+7&C(0?:0 =0H53> ?@09A-;8AB09&L&B!"&R!B@0=8F0 &P 87 &N&?'?(?)?" dXX333333?333333?U} }  } }  }  } } @            @ ?@   @ S?@   @ RQ?@   @ Q?@   @ V-?@    @  MbX9? ?@                                   !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~                                          !!!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&'''''''((((((()))))))*******+++++++,,,,,,,-------.......///////0000000111111122222223333333444444455555556666666777777788888889999999:::::::;;;;;;;<<<<<<<=======>>>>>>>???????@@@@@@@AAAAAAABBBBBBBCCCCCCCDDDDDDDEEEEEEEFFFFFFFGGGGGGGHHHHHHHIIIIIIIJJJJJJJKKKKKKKLLLLLLLMMMMMMMNNNNNNNOOOOOOOPPPPPPPQQQQQQQRRRRRRRSSSSSSSTTTTTTTUUUUUUUVVVVVVVWWWWWWWXXXXXXXYYYYYYYZZZZZZZ[[[[[[[\\\\\\\]]]]]]]^^^^^^^_______```````aaaaaaabbbbbbbcccccccdddddddeeeeeeefffffffggggggghhhhhhhiiiiiiijjjjjjjkkkkkkklllllllmmmmmmmnnnnnnnooooooopppppppqqqqqqqrrrrrrrssssssstttttttuuuuuuuvvvvvvvwwwwwwwxxxxxxxyyyyyyyzzzzzzz{{{{{{{|||||||}}}}}}}~~~~~~~>@ddgg Root Entry FSummaryInformation( FWorkbook F<DocumentSummaryInformation8 F  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZxlrd-2.0.1/tests/samples/formula_test_names.xls000066400000000000000000000170001376464300000217110ustar00rootroot00000000000000ࡱ;   Root Entry  !"#$%&'()*+,-./0123456789:<?@AC  \pCalc Ba==@ 8@"1Arial1Arial1Arial1Arial1Arial GENERAL                + ) , *    `Sheet1 Sheet2 Sheet3,, binopbool  singlesumD +testchoose AB C"d0testifa b" tfunca! tfuncvarb unaryminusTjb( 3  @@   DescriptionDataUnary operationSingle param sum Function callFunction with variable # args If functionChoose functionBinary operation ! bool  cc   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U } } v }     C @C @C @C Cb CC CPH0(  >@ gg   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }  PH 0(  >@ gg   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }  PH0 0(   >@gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0HPh Thomas KluyverThomas Kluyver21@"n@@g`@q՜.+,D՜.+,\Root EntryFWorkbookCompObj;IOle =SummaryInformation(>DocumentSummaryInformation8Btxlrd-2.0.1/tests/samples/formula_test_sjmachin.xls000066400000000000000000000410001376464300000223770ustar00rootroot00000000000000ࡱ>  Root Entry F'@1Workbook3SummaryInformation(DocumentSummaryInformation8 \p John Machin Ba==ZSC6$8X@"1Arial1Arial1Arial1Arial1Arial1Arial1h8Cambria1,8Calibri18Calibri18Calibri1Calibri1Calibri1<Calibri1>Calibri1?Calibri14Calibri14Calibri1 Calibri1 Calibri1Calibri1Calibri1 Calibri1Calibri"$"#,##0;\-"$"#,##0"$"#,##0;[Red]\-"$"#,##0"$"#,##0.00;\-"$"#,##0.00#"$"#,##0.00;[Red]\-"$"#,##0.005*0_-"$"* #,##0_-;\-"$"* #,##0_-;_-"$"* "-"_-;_-@_-,)'_-* #,##0_-;\-* #,##0_-;_-* "-"_-;_-@_-=,8_-"$"* #,##0.00_-;\-"$"* #,##0.00_-;_-"$"* "-"??_-;_-@_-4+/_-* #,##0.00_-;\-* #,##0.00_-;_-* "-"??_-;_-@_-                                                                      ff + ) , *     P  P        `            a>    ||?}}A} _-;_-* "ef-@_- }A} _-;_-* "ef-@_- }A} _-;_-* "ef-@_- }A} _-;_-* "ef-@_- }A} _-;_-* "ef-@_- }A} _-;_-* "ef -@_- }A} _-;_-* "L-@_- }A} _-;_-* "L-@_- }A} _-;_-* "L-@_- }A} _-;_-* "L-@_- }A} _-;_-* "L-@_- }A} _-;_-* "L -@_- }A} _-;_-* "23-@_- }A} _-;_-* "23-@_- }A} _-;_-* "23-@_- }A} _-;_-* "23-@_- }A}  _-;_-* "23-@_- }A}! _-;_-* "23 -@_- }A}" _-;_-* "-@_- }A}# _-;_-* "-@_- }A}$ _-;_-* "-@_- }A}% _-;_-* "-@_- }A}& _-;_-* "-@_- }A}' _-;_-* " -@_- }A}( _-;_-* "-@_- }}) }_-;_-* "-@_-    }}* _-;_-* "-@_- ??? ??? ??? ???}-}/ _-;_-* "}A}0 a_-;_-* "-@_- }A}1 _-;_-* "-@_- }A}2 _-;_-* "?-@_- }A}3 _-;_-* "23-@_- }-}4 _-;_-* "}}5 ??v_-;_-* "̙-@_-    }A}6 }_-;_-* "-@_- }A}7 e_-;_-* "-@_- }x}8_-;_-* "-@_  }}9 ???_-;_-* "-@_??? ???  ??? ???}-}; _-;_-* "}U}< _-;_-* "-@_ }-}= _-;_-* " 20% - Accent1M 20% - Accent1 ef % 20% - Accent2M" 20% - Accent2 ef % 20% - Accent3M& 20% - Accent3 ef % 20% - Accent4M* 20% - Accent4 ef % 20% - Accent5M. 20% - Accent5 ef % 20% - Accent6M2 20% - Accent6  ef % 40% - Accent1M 40% - Accent1 L % 40% - Accent2M# 40% - Accent2 L湸 % 40% - Accent3M' 40% - Accent3 L % 40% - Accent4M+ 40% - Accent4 L % 40% - Accent5M/ 40% - Accent5 L % 40% - Accent6M3 40% - Accent6  Lմ % 60% - Accent1M 60% - Accent1 23 % 60% - Accent2M$ 60% - Accent2 23ٗ % 60% - Accent3M( 60% - Accent3 23֚ % 60% - Accent4M, 60% - Accent4 23 % 60% - Accent5M0 60% - Accent5 23 %! 60% - Accent6M4 60% - Accent6  23 % "Accent1AAccent1 O % #Accent2A!Accent2 PM % $Accent3A%Accent3 Y % %Accent4A)Accent4 d % &Accent5A-Accent5 K % 'Accent6A1Accent6  F %(Bad9Bad  %) Calculation Calculation  }% * Check Cell Check Cell  %????????? ???+ Comma,( Comma [0]-&Currency.. Currency [0]/Explanatory TextG5Explanatory Text % 0Good;Good  a%1 Heading 1G Heading 1 I}%O2 Heading 2G Heading 2 I}%?3 Heading 3G Heading 3 I}%234 Heading 49 Heading 4 I}% 5InputuInput ̙ ??v% 6 Linked CellK Linked Cell }% 7NeutralANeutral  e%"Normal 8Noteb Note   9OutputwOutput  ???%????????? ???:$Percent ;Title1Title I}% <TotalMTotal %OO= Warning Text? Warning Text %XTableStyleMedium9PivotStyleLight16`*Sheet1n0Sheet21Sheet3= DescriptionDataNon-latin1 text ! >A:20 formula textformula zero-length textformula boolean formula errorformula numberformula non-latin1 text) X*ccB  {-/  dMbP?_*+%&?'?(?)?MnMicrosoft Office Document Imag/ dLetterwidm" d??&U} $} m > >   $I$I? + %ABC@DEF@ ABCDEF "4 fooA )@@@   D ! >A:20=/J4;/>@7ggD  1  dMbP?_*+%&?'?(?)?"??&U>@7ggD  3  dMbP?_*+%&?'?(?)?"??&U>@7ggD  Oh+'0@H\p  John Machin John MachinMicrosoft Excel@$@3[0՜.+,0 PXd lt|  Sheet1Sheet2Sheet3  Worksheets F&Microsoft Office Excel 2003 WorksheetBiff8Excel.Sheet.89qCompObj rxlrd-2.0.1/tests/samples/issue20.xls000066400000000000000000000140001376464300000173110ustar00rootroot00000000000000ࡱ>   u'ɀ\punics Ba==`T%8X@"1Sans1Sans1Sans1Sans1Sans$#,##0_);($#,##0)$#,##0_);[Red]($#,##0)$#,##0.00_);($#,##0.00)!$#,##0.00_);[Red]($#,##0.00)/**_($* #,##0_);_($* (#,##0);_($* "-"_);_(@_),)'_(* #,##0_);_(* (#,##0);_(* "-"_);_(@_)7,2_($* #,##0.00_);_($* (#,##0.00);_($* "-"??_);_(@_)4+/_(* #,##0.00_);_(* (#,##0.00);_(* "-"??_);_(@_)                + ) , *    `83ffff̙̙3f3fff3f3f33333f33BSheet1^ Sheet2 Sheet3    Sheet1    Sheet2    Sheet3'aasaasasalegenda as  u'ɀ   dMbP?_*+% &L&C&[TAB]&R&L&CPage &[PAGE]&R&?'?(?)?"d"XX??U} $ } $ } $ } $ } $ } $ } $ } $                  (,|rrr>@      ggD u'ɀ   dMbP?_*+% &L&C&[TAB]&R&L&CPage &[PAGE]&R&?'?(?)?"d"XX??U} $ >@ggD u'ɀ   dMbP?_*+% &L&C&[TAB]&R&L&CPage &[PAGE]&R&?'?(?)?"d"XX??U} $ >@ggD  ՜.+,0 Oh+'0@ ( 4@4G@YX  !"#$%&'()*+,-./0123456789;=Root EntryWorkbooknDocumentSummaryInformation8:HSummaryInformation(<pxlrd-2.0.1/tests/samples/namesdemo.xls000066400000000000000000000540001376464300000177730ustar00rootroot00000000000000ࡱ> *) \pStephen John Machin Ba==8X@"1}Arial1}Arial1}Arial1}Arial1}Arial"$"#,##0;\-"$"#,##0"$"#,##0;[Red]\-"$"#,##0"$"#,##0.00;\-"$"#,##0.00#"$"#,##0.00;[Red]\-"$"#,##0.005*0_-"$"* #,##0_-;\-"$"* #,##0_-;_-"$"* "-"_-;_-@_-,)'_-* #,##0_-;\-* #,##0_-;_-* "-"_-;_-@_-=,8_-"$"* #,##0.00_-;\-"$"* #,##0.00_-;_-"$"* "-"??_-;_-@_-4+/_-* #,##0.00_-;\-* #,##0.00_-;_-* "-"??_-;_-@_-                + ) , *    ` Sheet1 Sheet2Sheet3/Seamus O'Reilly=& A1Z10;" addnumstr{456A"all_local_ranges)9 @9 @9$ Apostrophe; # ASCII_Stringascii) BottomLine) 9#! EmptyString" Expenses; Faux) Intersection) ##%List)#@# LocalRange: localRange: Localrange:  Moscow;  NegInt numCatNum{, numcatnum2Gz(@EdL@  PosFloat@PosInt  ; * );;  Profit; $ rectangle1; $ rectangle2; % RelativeNeg; % RelativePos;  Sales;  twofivesix) UnicodeString"Union) ##vrai" Year_Tot; & >A:20; "wJanFebMarAprMayJunJulAugSepOctNovDecYear TotSalesExpensesProfit D <cc   r  dMbP?_*+%"??U   E@ N # 7   >@  7    dMbP?_*+%"??U>@7  '/  dMbP?_*+%MHP Mobile Printing PSS odXXLetterPRIV0''''$\KhC#[$IUPH dLetter [none] [none]Arial4Pd?SJM_2<Automatic>dEXCEL.EXE"dXX??U} $                      @J@ .@4NI % #J@C?@@ @* 0@@@P@`@p@@ @@@@( @) ## @ #ً( h@ ) ## h@  # (/@ #@#B/@  # ًC C @C@CC@C&2@@  LCascii,L{ 123456&2@@ <LC@LC 1234562 PLC )\LGz(@EdL@ 12.3456.789&1@ @ @@AtLC 12.3456.789p@C2P$2"."OOOImR  >@_ 7  h00  dMbP?_*+%"??U~ E@">@7 Oh+'0@H\p  John Machin John MachinMicrosoft Excel@a@==՜.+,0  PXp x Lingfo Pty Ltd Sheet1Sheet2Sheet3Seamus O'ReillyA1Z10 Apostrophe ExpensesSheet1!LocalRangeSheet2!localRangeSheet3!LocalrangeSheet3!Print_AreaSheet3!Print_TitlesProfit rectangle1 rectangle2 RelativeNeg RelativePosSales Year_Tot  Worksheets Named Ranges  "#$%&'(Root Entry F[Workbook0SummaryInformation(DocumentSummaryInformation8!xlrd-2.0.1/tests/samples/picture_in_cell.xls000066400000000000000000000150001376464300000211600ustar00rootroot00000000000000ࡱ;   Root Entry  !"#$%&'()*,/013  \pCalc Ba==@ 8@"1Arial1Arial1Arial1Arial GENERAL                + ) , *  x `Sheet1,,T~bvΘ[uRnJΘ[uPNG  IHDRPsHIDATxAHZqǿJ4VeV^MCŰd޾ 'qf0`'SoP[MLLRbNl̿0%_a6p6~401>Qô/Dn7ڪ* VWop `]wVke(fffy$02 n7N'@ V!ieH. K#| ]mR I`'@Ibqq^X l‹`>Vz惔VC=*Jr9lmmnSڠP(0::zrp~r J&-RU#4R$IlooG*9jB! 7SFO^!]81_;&\u" hoEՂ] o}}VlppKKKH&'"6n*t+$rbmmB,'ǃή hR"["Qcg RF]}>[T/ 69m5U6] CEWY?0BAwKvIENDB`3  @@  cc   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?"d,,333333?333333?U} st@(    dA ?Graphics 1'S]`>@_  gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0@H d p | Christopher Withers5@@@lNI@b.O՜.+,D՜.+,\Root EntryF Workbook CompObj+IOle -SummaryInformation(.DocumentSummaryInformation82txlrd-2.0.1/tests/samples/profiles.xls000066400000000000000000001020001376464300000176400ustar00rootroot00000000000000ࡱ; ?<  !"#$%&'()*+,-./0123456789:;>@Root Entry  \pmanfred Ba= =@ 8@"1Arial1Arial1Arial1Arial1Arial GENERAL0 0.000 0.00%                + ) , *  " " "X "   "     83ffff̙̙3f3fff3f3f33333f33333`H PROFILEDEF*AXISDEF(TRAVERSALCHAINAGE'AXISDATUMLEVELS, PROFILELEVELS11Tzr83  @@  98PROFILabcdefghijklP8.2P8.3P8.4P8.5P8.6P8.7P9P9.1P9.2P9.3P9.4P9.5P9.6Q0 Quergefllei1h3h2h1a1a2b1c1f1g1l1l2j1k1j3j4g3g4d1c3e1f3f2g2b2c2j2k2 8 cc   dMbP?_%*+&C&P&C&F&333333?'333333?(-؂-؂?)[[?" d,,??U } #}  }                                 N N N  N"&*.26:>BFJN NRVZ^bfjnrvz~ N N N  N "&*.26:> N BFJNRVZ^bfjn N rvz~ N  N  N "&*. PH0(  >@gg   dMbP?_%*+&C&P&C&F&333333?'333333?(-؂-؂?)[[?" d,,??U } #} } }                           "   &   *   .   2   6  : PH 0(  >@gg   dMbP?_%*+%"&C&"Times New Roman,Standard"&12&A+(&C&"Times New Roman,Standard"&12Seite &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }                                ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?{ͯf@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ aL @ (@ *@~   ~ o|?Ù?̯f@ @ @@33333@@ L @ (@ *@~   ~  o|? Ù? ̯f@  @  @ @33333@ @ L @ (@ *@~  ~  ? !!? La@ ; @ p @ ]W 3@ ]0}@  'CL @ Aa(@ /*@ A*@ ~  GK? TL? w6^@ {` @ `" @ Apf3@ ҙ@ ;L @ 6(@ H#*@ L +@ ~  <&? A? }@ ] @  ] @ O0e543@ Zc&ٚ@ FalM @ d(@  ǃ*@ VH/+@ ~  %?  ? 8l@ sc @ o@ @ OoC3@ u@  MM @ .6%)@ Wq*@ K}:[+@ ~ ?I!4?z P@* 7 @? $7 @ù= @?@ >mzP>@ Fcg')@ U8e*@ <^+@PH0 0(   >@gg   dMbP?_%*+%"&C&"Times New Roman,Standard"&12&A+(&C&"Times New Roman,Standard"&12Seite &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }           dp@eάp@ pe1p@m2p@ =f2p@T:Xgp@  3mʒp@!%4p@ 9̗p@p@ a+ep@eΫp@ iap@R!p@ |ap@cڬp@  Xp@ W/p@  1ZGUp@ qqtp@  qp@ ۧ1p@  5;Np@ p@  EJp@ !p@ ^p@Ji׶p@PH@0(  >@gg   dMbP?_%*+%"&C&"Times New Roman,Standard"&12&A+(&C&"Times New Roman,Standard"&12Seite &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }               ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7  ?!dp@ [29p@DDZ Z%([ڏp@DQ?([p@ D)%Ёp@DDZ%뭁p@DQ?2h!p@DDZZ2 = By_p@DDZZ2 zNz_p@DDZZ2 2@p@DDZZ2  3mJp@DDZ Z% [Ɏ p@D Q?2([ڒp@DDZ Z2[닄6p@DDZ Z%9p@DQ?%ҩ+ep@DV-?%"p@ D?ZZ6-}up@DDZZ2 뭁p@DDZZ2 e/sp@DDZZ2 W[p@D Q?2x $(p@DDZ Z2:Mp@DDZ Z%Oep@DQ?% gp@DV-?%tˑp@D Q?%z6>p@DbX9?cڬp@Z6bp@ D?ZZ609|p@ D?ZZ%SVӕp@Dy?%ɯp@D?ɯp@Dfffffp@fffffp@fffffp@fffffp@  ?! Xp@ [ 2 $ p@D D Z Z % %Ώp@D Q? %p@ D ) Cxqāp@D D Z % 9p@D Q?2 p@D D Z Z 2 Sp@D D Z Z 2 nSp@D D Z Z 2 5p@D D Z Z 2 W&R?p@D D Z Z % MOXp@D Q?2 %Βp@D D Z Z 2 *p@D D Z Z % $ p@D Q?%  Yp@D V-?% Y/rp@D Q?% &P6p@D bX9? W/p@Z 6 }k/p@ D ?Z Z 6 Ip@ D ?Z Z %  #bp@D y?% SVӕp@D ? SVӕp@D  fffffp@ fffffp@ fffffp@ fffffp@   R O?! 1ZGUp@ [ 2 Sp@D D Z Z % |Yp@D Q? |Yp@ D ) <3p@D D Z % 2双xp@D Q?2 Ge΢p@D D Z Z 2 JÏLp@D D Z Z 2 \p@D D Z Z 2 f46@p@D D Z Z 2 Lp@D D Z Z % B #ۓp@D Q?2 N{&p@D D Z Z 2 hp@D D Z Z % %p@D Q?% j:p@D V-?% =ئ}p@D Q?% ~p@D bX9? qqtp@Z 6 op@ D ?Z Z 6 ]p@ D ?Z Z % I/p@D y?% *|bp@D ? *|bp@D  fffffp@ fffffp@ fffffp@ fffffp@   Ѳ ё?! qp@ [ 2 {[#ap@D D Z Z % qp@D Q? qp@ D )  (߃p@D D Z % IKp@D Q?2 mГp@D D Z Z 2 28p@D D Z Z 2 2ʙޒp@D D Z Z 2 V6ɒp@D D Z Z 2 Qp@D D Z Z % Eap@D Q?2 mLmp@D D Z Z 2 Quؔp@D D Z Z % w#SՖp@D Q?% sD#p@D V-?% `[p@D Q?% }wp@D bX9? ۧ1p@Z 6 r p@ D ?Z Z 6 =h $p@ D ?Z Z % :=p@D y?%  n5pp@D ?  n5pp@D  fffffp@ fffffp@ fffffp@ fffffp@   sMiX?! 5;Np@ [ 2 "+}p@D D Z Z % Ӈ yp@D Q? Ӈ yp@ D ) ]p@D D Z % Js p@D Q?2 WTp@D D Z Z 2  *p@D D Z Z 2 ]͵p@D D Z Z 2 l˧p@D D Z Z 2 g%| p@D D Z Z % ]NأΕp@D Q?2 -ؕp@D D Z Z 2 Ap@D D Z Z % ( p@D Q?% cp@D V-?% vcp@D y?% :qp@D ? :qp@D  fffffp@ fffffp@ fffffp@ fffffp@   2.(v?! EJp@ [ 2 K5p@D D Z Z % AّŖp@D Q? Aّņp@ D ) b2p@D D Z % X1p@D Q?2 j˘-p@D D Z Z 2 Ip@D D Z Z 2 {jp@D D Z Z 2 +t$ۖp@D D Z Z 2 <p@D D Z Z % 2/MJp@D Q?2 ¨Top@D D Z Z 2 (Np@D D Z Z % LŬp@D Q?% C>p@D V-?% 5K8p@D Q?% Mۼ,ܛp@D bX9? !p@Z 6  9Gp@ D ?Z Z 6 z(p@ D ?Z Z % XHtBp@D y?% G{up@D ? G{up@D  fffffp@ fffffp@ fffffp@ fffffp@  ~ !^p@ [2^p@DDZ Z%- p@DQ?- p@ D)- p@DDZ% p@DQ?2- p@DDZZ2 - p@DDZZ2 - p@DDZZ2 - p@DDZZ2 - p@DDZ Z%  p@D Q?2- p@DDZ Z2- p@DDZ Z%^p@DQ?%aӫp@DV-?%^p@D Q?%X7p@DbX9?Ji׶p@Z6-_p@ D?ZZ6/%-"yp@ D?ZZ%ɾƻp@Dy?%Źp@D?Źp@Dfffffp@fffffp@fffffp@fffffp@PHP0(  >@gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0@H ` l x Manfred Moitzi33@@@@w4՜.+,D՜.+,\Root EntryFp=@WorkbooksCompObjIOle SummaryInformation(DocumentSummaryInformation8txlrd-2.0.1/tests/samples/ragged.xls000066400000000000000000000150001376464300000172510ustar00rootroot00000000000000ࡱ;   Root Entry  !"#$%&'()*+,-/2346  \pCalc Ba==@ 8@"1Arial1Arial1Arial1Arial GENERAL                + ) , *  `Sheet1Sheet2 Sheet3,,Tjb( 3  @@  8 abcdefghIjkl x cc   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }               PH0(  >@gg   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }  PH 0(  >@gg   dMbP?_%*+$!&C&"Times New Roman,Regular"&12&A)&&C&"Times New Roman,Regular"&12Page &P&333333?'333333?(-؂-?)-؂-?" d,,333333?333333?U }  PH0 0(   >@gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0HPh Thomas KluyverThomas Kluyver3@@@n@ɪ[՜.+,D՜.+,\Root EntryF Workbook| CompObj.IOle 0SummaryInformation(1DocumentSummaryInformation85txlrd-2.0.1/tests/samples/sample.ods000066400000000000000000000153501376464300000172700ustar00rootroot00000000000000PKAQl9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKAQ0UThumbnails/thumbnail.pngPNG  IHDR'e PLTE~OEIDATx  OmcBAUpIENDB`PKAQ settings.xmlY]s8}_n<͒m0d}4u=ĵXvf,s7 $E޷'Cezݴ ~łzEpՖEv.loE;H$'HGymNj,a?Rcqħ'(vNW[= T"!QLPRO*yA1 " UЇW(7E=S}kHѧ/+ӫm8gӪr$n;GF0h3b0(*:}$,*D> KHr_wW)h1mn fppO;lԽn* ;Xo[ekA0Vrq4U-58NKEy؜n{ޫiA+n&ƣ1eѓDe L'#d(Jһ8EՀSDjQz2b$M$:@҃#`LPI?K:$qV .' D*( ~l2fa RrЙf+—N»ܧk*+7^Nt2Jw[~V$Om ^r|L2\j W-bUҘ$lL %>Q)~tNCi._0TI(z}'vH0/b?h׽ A xքJaȶ[ _>"Rr sVDO{lA(@&jp?!i~쬷/{~#U3A ǐ~8_83aA$\]SN6)eݗN+PKʇl EPKAQConfigurations2/accelerator/PKAQConfigurations2/toolpanel/PKAQConfigurations2/menubar/PKAQConfigurations2/floater/PKAQConfigurations2/statusbar/PKAQConfigurations2/progressbar/PKAQConfigurations2/toolbar/PKAQConfigurations2/images/Bitmaps/PKAQConfigurations2/popupmenu/PKAQ manifest.rdf͓n0~yy.8浂K.mnڔfVشd lx+({VӰV;bYΦpvprs;8fg5sI鹞K>YIrYWsJI698W6Ml6Km z~~av\UP /fj=Vcsyl,0Ǿje"9/PXvױH3?03g<&oMss>7 ׋ǎRōfoE| >wn#9|ȸVEp|"aMl_^(c%3ar@oXn~&nBQΆbCL_Gi.a@=f8XK7p?. ɀSF)&-E,(¨ t3DY4^WiFFY~sk7Vm?C&^ JsaA ,r|#q piw/mo{ 0Bzē*!7ViLO (1.l#!*86#3"ޗK)YIm9ցu+"NUgxq NB;׫aNv xZIόInEӽ}ވߡI?3Li3#v@<^k\ѕIaln@Z_Z=EidNO_0DK&:;_ >Xfn.[u-'_Wu2#A]3iQd5P`CIK[FȌ3Km޽UO" xqo@U $1/Xwȍ޴Wkc<_+x Y贏BG#PKڌdPKAQ styles.xmlZmo6_!)}A۵݀&(X0%D~GR7Wqꤘ$;)7)s6Dggn0]d!h?N/ͥfv*05o MV0^sYQ)__r}_% 2*!xײEn{ͤaARs'H~5Uw-2T/%n"Fbh€[\Jr(C<]Ơ#6>O'OC82oIpR Ծ4:)qgϡ3ʎz*}vLl]1)j7-yVU ƙcV4o(n?(_eV^xV[DŽJU&ߣc~tIrw%4o/I%!MZVDXVꗮ} ;m0[+q{ |o/PNe0x ]ݗFM/*Ƒ-ߵ,s[)QRZPKw;l$PKAQmeta.xmlMO0 *pmR+7N mJodJu?]!T)k'aF{p]cф1x Xݔvjmn2 Shv+Fn^ 3KZ&6g O4n)mw)00t0M(M %kрOv O3vZlCeoEq3TlThF9>qHFbbJI!X<ϋ쁖JTt]7*u/bew hfoPc5p[W=7?Γ0NB^z}\~2Ϣ buv({N%eI^,<{`+4K8۹] _{/PKy{vPKAQMETA-INF/manifest.xmlSAn T89D P8HxAD8qUEXvݹw bYzmkdj+PwnerDUI!Ijڷ$/G[53fWj6iiPK!`,m xl/styles.binJA콁S  (B1ФRPegcEq`Ϝqȕ0>ڒGt"6}p8w%O_?\8ΜXNB>PK!Q&#xl/worksheets/_rels/sheet1.bin.relslj0 `t_0ƈSak@4eJm?U(lkZP6:ϳ;()d`'pxϴb%Y|UK)Ck &&⺙bXj̳Nh8>Ώ O<eO;xĩ46T1#;ڪybw55zpPK!oJ׵wxl/worksheets/sheet1.binjdd(~ C3Q,D22t2aFLA"bD1Tr0q31GR.e1a$n%N91G7Z(Lj$>{ҿQ>HjlIh촪b-{gv|g'=/:~E[7 r!7\&0;9$4 kas6r}SXiڽAa P=hU opc5(G>XkPwAЬ A)oVi5($9AWFYm3z脷`Ԭe DC]j9KXSGP@$I<^9B%'xGdA-Q WjQ'O"#CZ&bgHĔwAo@޾~Oo^ի7/ͭUYr(Yr|?ҩĿ_Ê Sw?]NLXxŰ@|/'1$P2׈p=l ,Y=#1(njNSs{r2q:sGj 镸T#l|HQ"'Xz;vcubL9l.ωCi 9:$1e"ls1ZH:O0x$]*'(\$k>5qC! L7a!\28p=0nul#$.G19`KvL~*N!DI;DPODI͊;|y3+~k:Gؕe#9t, }#lJNwp*s0[pe EK߸jP˽?=D@=)?xW;czz1{ۊեvO[$pIhODC8N5zupEOBUh⪾@3S;z!3Eˎ?cx;BBt.S ̲BR뤓fH=J⎯&:hn$?-6?9pd<4n(K\V_$ =f ]GB,lVgDA5MXȊ*LY5t .#U3ps:- j$+' U`MZ4)UBrF,jUTtg1kMزՊjcbifOSvmorVW 0xn?Gս@A0Y4rv6j׎?@"E-59 ^v|WjK+sV<1.wEЮ]!$MElk?ݠ_ J+zP)n zuV+^0]gz|>ܘ|Y{@yިvQj׻R0J~W4h[ ;[aԨQQ[R3պAY+OGf 0u?PK!ȕ7#1xl/worksheets/binaryIndex1.binҒ` 1#*Ƞ dMebPK!lRIwdocProps/core.xml (]O0MBa8?8ͮ41im#m-!f=}zΛ拝o0+Y$Q*@*AuD2R+ jEy~SQe( q'IQ]s:-b#^\+#GO<+,F0#푌He(Hgq%{rW&N]}})A;Gc4Q1|?=UC.]Q@ehF LyW*yT\(me&=9e{z_K O |l(uPjY\qƷU2.l6Rbb?y:!eJPK!aI docProps/app.xml (Ao0  9P bH;agMc$׏vڍ{xDI:_Pkx}, $jcJŝAmrL-QZIJsgۼih:$V P_@1%z:ځwZ}I;ko;#Ɔ\TLuU[k<9X7#(yG06eԪUb.]Ap*ћL lS3>!e+Ja;' \\ Kĝ#٘L ^ΉGw|әs|%,W\xŧˡڶ&C/pț~Y&>y~^.ʛu6SoPK-![f3 p[Content_Types].xmlPK-!CL _rels/.relsPK-!3 U-xl/_rels/workbook.bin.relsPK-!V`)xl/workbook.binPK-!`,m  xl/styles.binPK-!Q&#^ xl/worksheets/_rels/sheet1.bin.relsPK-!oJ׵w` xl/worksheets/sheet1.binPK-!9e K xl/theme/theme1.xmlPK-!ȕ7#1xl/worksheets/binaryIndex1.binPK-!lRIw^docProps/core.xmlPK-!aI docProps/app.xmlPK xlrd-2.0.1/tests/samples/sample.xlsx000066400000000000000000000101331376464300000174730ustar00rootroot00000000000000PK=@Bxl/comments1.xml]N@ DXNr@J@m"eڭc$n3M* K_I >?^ 4W_wM0-;DG.P[!sێKctd2C=bI G:XDO>y a^_i;]ޭ (Et Wj9PKHHI+PK=@Bxl/drawings/vmlDrawing1.vmlRێ02T(jBH+Wl Dzi3N.3g\d U\˛3$t=5~zYD:aUMr7|@uG9(+ xCI O**)5yU dl>zZJGk<^KqǹtT+|<TvuWFaI՝u蒳R`U&O*bkTzĪ8dU.09G._t'pS%1#mez GCQGFt)Z*ᯟkc$di f3^1vp`reGΖ;#O#958 HCDTM# }P>*`T5wʘHo4TprV+O_I{i$aK=[amg_ LB޺L"1R JkPK`#>PK=@Bxl/worksheets/sheet1.xml͎0`߁.U.<1mٟs-4߂Jp6N~Nm `@E8'hEB c:c5$lӧAs% <@ۄU wr494*&4"3y.8 B H漾-Ee4ՎpJpsrT77ZAH)FS]<G!^朐Ƙ ݙ4Q3_냽'Hjfh_ӸGh8Fx,!sBӘj}?LkP(JG䬖 \sp p{9c 㗝aD;C5NPKq\PK=@B#xl/worksheets/_rels/sheet1.xml.relsj1 E`5E)%lB 0aktr:Z袻+=h9E9V780xwϠPp.,^$BJI/b'$:&u31Ⱦȸj'̷ mr.ӽ4u$-KNRSjD,J[\'R]j:2n4N]Իl&ٷXꜚ94(YI $KO`{Sx菧ȧnR L^]_!2;PK6bPK=@Bxl/workbook.xmlKn @O; I[Ue;R6U 0QX qwؖl10o0s_rxǂ|,6a^K<wS‰Qǂ)5jpO;UN&Zƣ&X$gj&4YUeuv DXGpΨ0TiHdt z͝Sss dCcM^-9l`,&kNFp~['||TL8k9WeqY?5ug^:jkd7[ek,p3C^p2'"ŕ/#Ƣ?PKm<PK=@Bxl/_rels/workbook.xml.relsJ0wsiWM"^>@HM6 3Oެb{ 0AyRHβN- cdOM;|IHw%3tƭJ|rF |糫rX&;*yRTH.O*-|+@a(n.Kt+ؘ| #`Ur4 R˭Lƾګ?D4? "3; MQ^\{F#{fl7HD "1Y#"suݓCy{Pκ=;ޯo B߁Iv5|sC>PK[22PK=@BHHI+xl/comments1.xmlPK=@B`#>xl/drawings/vmlDrawing1.vmlPK=@Bq\)xl/worksheets/sheet1.xmlPK=@BWi#xl/worksheets/_rels/sheet1.xml.relsPK=@Bttxl/sharedStrings.xmlPK=@B6b xl/styles.xmlPK=@Bm<xl/workbook.xmlPK=@B&5- xl/_rels/workbook.xml.relsPK=@Bi뚲( M _rels/.relsPK=@B[228 [Content_Types].xmlPK  xlrd-2.0.1/tests/samples/sample.zip000066400000000000000000000002521376464300000173000ustar00rootroot00000000000000PK AQ sample.txtUT *_*_ux PK AQ sample.txtUT*_ux PKPDxlrd-2.0.1/tests/samples/xf_class.xls000066400000000000000000000550001376464300000176260ustar00rootroot00000000000000ࡱ> *+  !"#$%&'()Root Entry F>ӓbYWorkbookKOle SummaryInformation( Oh+'0T(0 @Lmanfred@Y՜.+,0HP X`hp x  table1table2table3 Arbeitsbltter \pmanfred Ba==?8X@"1Calibri1Arial1Arial1Arial1 Calibri1Calibri1Calibri1Calibri1Calibri1 Calibri1?Calibri14Calibri1>Calibri1Calibri1Calibri1Calibri1<Calibri1Calibri1h8Cambria1,8Calibri18Calibri18Calibri14Calibri1 Calibri1 Calibri3#,##0\ " ";\-#,##0\ " "=#,##0\ " ";[Red]\-#,##0\ " "?#,##0.00\ " ";\-#,##0.00\ " "I"#,##0.00\ " ";[Red]\-#,##0.00\ " "q*6_-* #,##0\ " "_-;\-* #,##0\ " "_-;_-* "-"\ " "_-;_-@_-k)3_-* #,##0\ _ _-;\-* #,##0\ _ _-;_-* "-"\ _ _-;_-@_-,>_-* #,##0.00\ " "_-;\-* #,##0.00\ " "_-;_-* "-"??\ " "_-;_-@_-{+;_-* #,##0.00\ _ _-;\-* #,##0.00\ _ _-;_-* "-"??\ _ _-;_-@_-                                                                     + )     a>               P  P     ` , *   ff  H    H    H   " #     ! 0" 0  ||LV!}A} _ _-ef#0.0}A} _ _-ef#0.0}A} _ _-ef#0.0}A} _ _-ef#0.0}A} _ _-ef#0.0}A} _ _-ef #0.0}A} _ _-L#0.0}A} _ _-L#0.0}A} _ _-L#0.0}A} _ _-L#0.0}A} _ _-L#0.0}A} _ _-L #0.0}A} _ _-23#0.0}A} _ _-23#0.0}A} _ _-23#0.0}A} _ _-23#0.0}A}  _ _-23#0.0}A}! _ _-23 #0.0}A}" _ _-#0.0}A}# _ _-#0.0}A}$ _ _-#0.0}A}% _ _-#0.0}A}& _ _-#0.0}A}' _ _- #0.0}}( ???_ _-#0.0???-;_-????\ _ ???@_-@ ???}}) }_ _-#0.0-;_-?\ _ @_-@ }}, ??v_ _-̙#0.0-;_-?\ _ @_-@ }U}- _ _-#0.0-;_-}-}. _ _-}A}/ a_ _-#0.0}A}0 e_ _-#0.0}x}1_ _-#0-; ?\ @_}A}3 _ _-#0}-}4 _ _-}A}5 _ _-#0}A}6 _ _-?#0}A}7 _ _-23#0}-}8 _ _-}A}9 }_ _-#0}-}< _ _-}}= _ _-#0???-; ????\  ???@_-@ ???}d}J_ _-#0 P???-; P????\ 20% - Akzent1M 20% - Akzent1 ef % 20% - Akzent2M" 20% - Akzent2 ef % 20% - Akzent3M& 20% - Akzent3 ef % 20% - Akzent4M* 20% - Akzent4 ef % 20% - Akzent5M. 20% - Akzent5 ef % 20% - Akzent6M2 20% - Akzent6  ef % 40% - Akzent1M 40% - Akzent1 L % 40% - Akzent2M# 40% - Akzent2 L湸 % 40% - Akzent3M' 40% - Akzent3 L % 40% - Akzent4M+ 40% - Akzent4 L % 40% - Akzent5M/ 40% - Akzent5 L % 40% - Akzent6M3 40% - Akzent6  Lմ % 60% - Akzent1M 60% - Akzent1 23 % 60% - Akzent2M$ 60% - Akzent2 23ٗ % 60% - Akzent3M( 60% - Akzent3 23֚ % 60% - Akzent4M, 60% - Akzent4 23 % 60% - Akzent5M0 60% - Akzent5 23 %! 60% - Akzent6M4 60% - Akzent6  23 % "Akzent1AAkzent1 O % #Akzent2A!Akzent2 PM % $Akzent3A%Akzent3 Y % %Akzent4A)Akzent4 d % &Akzent5A-Akzent5 K % 'Akzent6A1Akzent6  F % (AusgabeyAusgabe  ???%????????? ???) Berechnung Berechnung  }% *$Dezimal+, Dezimal [0] ,EingabeyEingabe ̙ ??v%  -ErgebnisSErgebnis %OO.Erklrender TextG5Erklrender Text %/Gut9Gut  a% 0NeutralANeutral  e% 1Notizd Notiz  2$Prozent 3SchlechtCSchlecht  %&Standard4 berschrift= berschrift I}%5 berschrift 1O berschrift 1 I}%O6 berschrift 2O berschrift 2 I}%?7 berschrift 3O berschrift 3 I}%238 berschrift 4A berschrift 4 I}%9Verknpfte ZelleUVerknpfte Zelle }%:$Whrung;, Whrung [0]<Warnender TextC Warnender Text %=Zelle berprfenZelle berprfen  %????????? ???XTableStyleMedium9PivotStyleLight16`;table1,Gtable2pItable311d REDGREENBLUELEFTCENTERRIGHTTOPMIDDLEBOTTOM borderstyleMERGED- .Hcc PK![Content_Types].xmlj0Eжr(΢Iw},-j4 wP-t#bΙ{UTU^hd}㨫)*1P' ^W0)T9<l#$yi};~@(Hu* Dנz/0ǰ $ X3aZ,D0j~3߶b~i>3\`?/[G\!-Rk.sԻ..a濭?PK!֧6 _rels/.relsj0 }Q%v/C/}(h"O = C?hv=Ʌ%[xp{۵_Pѣ<1H0ORBdJE4b$q_6LR7`0̞O,En7Lib/SeеPK!kytheme/theme/themeManager.xml M @}w7c(EbˮCAǠҟ7՛K Y, e.|,H,lxɴIsQ}#Ր ֵ+!,^$j=GW)E+& 8PK!{#F theme/theme/theme1.xmlYo5#?Xs/&4(q|;q7P9"!! č*ĥ5C|HxgfYH߷=nR:A,Np8!v)U믾r tBSJp}$Z[KK*iPfn,d eI8B)_ZY^^_Je EwA26U,΂%>GVf"r Z%9 U$X,ۿ`%*q`mm)QŴ5ho^ݮ[~*za[uinIryڽ嶏_ym89z抇 _÷7{uoA>\\ox J8ƠAA@r*2," -Ba4ˈt !zrґd`87)ÌP\w7r{gO%Ϟ>9yɣO{76dq}_|g߾7UG@ D?~~S¨*&="{"ŽYӑ|@xK}==&=Y9Ѭ$;B𮐍 cx4}Fwj6 ٗv#ٵ+2V*=xf%‘g9k+k !c<0t~c tnf0(Ysܘ-pz۰r6Ĺ}U ''6<X@gǤ@=1 uص;XR1T'4qUO^O،`2h۾st+,stkBdq\`je{ؐSO`52:W1 ԉ,',Hl@o Y|N濋9GÆ5dXt")Ŵd bv9 d=&ʝ#zHuSlR;sA49xrHU{] ݝ fܔmCSvyY{1/fmV dV+EؿYj]ƚZ)Zq~8Y5D9ɐSƦV_ 1t+ &A6Nn9!T[NFkeN{JF9]5g>;//Rم=]F˞Q'kiI(㓍|W"`7zXɱMq"a ;$6 8[6p-*r8Ĭb}7$(<ʡ, J]Z90N}`/.yX?U.h`KIY$~KNFXY(V3H q eu8?RhdcΑhn`Ϙ r#cs+3b3<6V6(`T&9ӆ\V-gKo#0, x޷UJ$6Q^PK! ѐ'theme/theme/_rels/themeManager.xml.relsM 0wooӺ&݈Э5 6?$Q ,.aic21h:qm@RN;d`o7gK(M&$R(.1r'JЊT8V"AȻHu}|$b{P8g/]QAsم(#L[PK-![Content_Types].xmlPK-!֧6 +_rels/.relsPK-!kytheme/theme/themeManager.xmlPK-!{#F theme/theme/theme1.xmlPK-! ѐ' theme/theme/_rels/themeManager.xml.relsPK]    `EF  dMbP?_*+%,&ffffff?'ffffff?(333333?)333333?MCanon MX850 series Printer ߁ 4d A4BJDM @Rt,T`Op,T`OpRt,TT`Op,TT,TT`OpXX'  d Rt RtH1 Rt   Canon MX850 series Printer ߁ 4d A452<" d,, ` `? ` `?&`U  ,,,,@;  ; > ? @ A B C I D E H F G J Bx**>@  gg  BHH  dMbP?_*+%,&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?&`U ,,,, K KK KKK KKK KKK <>@ gg  J.K  dMbP?_*+%,&ffffff?'ffffff?(333333?)333333?" d,, ` `? ` `?&`U ,,, > ? @ A B C (>@gg DocumentSummaryInformation8CompObjs F'Microsoft Office Excel 2003-Arbeitsbl.Biff8Excel.Sheet.89qxlrd-2.0.1/tests/test_biffh.py000066400000000000000000000010771376464300000163240ustar00rootroot00000000000000import sys import unittest from xlrd import biffh if sys.version_info[0] >= 3: from io import StringIO else: # Python 2.6+ does have the io module, but io.StringIO is strict about # unicode, which won't work for our test. from StringIO import StringIO class TestHexDump(unittest.TestCase): def test_hex_char_dump(self): sio = StringIO() biffh.hex_char_dump(b"abc\0e\01", 0, 6, fout=sio) s = sio.getvalue() assert "61 62 63 00 65 01" in s, s assert "abc~e?" in s, s if __name__=='__main__': unittest.main() xlrd-2.0.1/tests/test_cell.py000066400000000000000000000035551376464300000161700ustar00rootroot00000000000000# Portions Copyright (C) 2010, Manfred Moitzi under a BSD licence import unittest import xlrd from xlrd.timemachine import UNICODE_LITERAL from .helpers import from_sample class TestCell(unittest.TestCase): def setUp(self): self.book = xlrd.open_workbook(from_sample('profiles.xls'), formatting_info=True) self.sheet = self.book.sheet_by_name('PROFILEDEF') def test_empty_cell(self): sheet = self.book.sheet_by_name('TRAVERSALCHAINAGE') cell = sheet.cell(0, 0) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_EMPTY) self.assertEqual(cell.value, '') self.assertEqual(type(cell.value), type(UNICODE_LITERAL(''))) self.assertTrue(cell.xf_index > 0) def test_string_cell(self): cell = self.sheet.cell(0, 0) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_TEXT) self.assertEqual(cell.value, 'PROFIL') self.assertEqual(type(cell.value), type(UNICODE_LITERAL(''))) self.assertTrue(cell.xf_index > 0) def test_number_cell(self): cell = self.sheet.cell(1, 1) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_NUMBER) self.assertEqual(cell.value, 100) self.assertTrue(cell.xf_index > 0) def test_calculated_cell(self): sheet2 = self.book.sheet_by_name('PROFILELEVELS') cell = sheet2.cell(1, 3) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_NUMBER) self.assertAlmostEqual(cell.value, 265.131, places=3) self.assertTrue(cell.xf_index > 0) def test_merged_cells(self): book = xlrd.open_workbook(from_sample('xf_class.xls'), formatting_info=True) sheet3 = book.sheet_by_name('table2') row_lo, row_hi, col_lo, col_hi = sheet3.merged_cells[0] self.assertEqual(sheet3.cell(row_lo, col_lo).value, 'MERGED') self.assertEqual((row_lo, row_hi, col_lo, col_hi), (3, 7, 2, 5)) xlrd-2.0.1/tests/test_formats.py000066400000000000000000000056061376464300000167230ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Portions Copyright (C) 2010, Manfred Moitzi under a BSD licence import sys from unittest import TestCase import xlrd from .helpers import from_sample if sys.version_info[0] >= 3: def u(s): return s else: def u(s): return s.decode('utf-8') class TestCellContent(TestCase): def setUp(self): self.book = xlrd.open_workbook(from_sample('Formate.xls'), formatting_info=True) self.sheet = self.book.sheet_by_name(u('Blätt1')) def test_text_cells(self): for row, name in enumerate([u('Huber'), u('Äcker'), u('Öcker')]): cell = self.sheet.cell(row, 0) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_TEXT) self.assertEqual(cell.value, name) self.assertTrue(cell.xf_index > 0) def test_date_cells(self): # see also 'Dates in Excel spreadsheets' in the documentation # convert: xldate_as_tuple(float, book.datemode) -> (year, month, # day, hour, minutes, seconds) for row, date in [(0, 2741.), (1, 38406.), (2, 32266.)]: cell = self.sheet.cell(row, 1) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_DATE) self.assertEqual(cell.value, date) self.assertTrue(cell.xf_index > 0) def test_time_cells(self): # see also 'Dates in Excel spreadsheets' in the documentation # convert: xldate_as_tuple(float, book.datemode) -> (year, month, # day, hour, minutes, seconds) for row, time in [(3, .273611), (4, .538889), (5, .741123)]: cell = self.sheet.cell(row, 1) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_DATE) self.assertAlmostEqual(cell.value, time, places=6) self.assertTrue(cell.xf_index > 0) def test_percent_cells(self): for row, time in [(6, .974), (7, .124)]: cell = self.sheet.cell(row, 1) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_NUMBER) self.assertAlmostEqual(cell.value, time, places=3) self.assertTrue(cell.xf_index > 0) def test_currency_cells(self): for row, time in [(8, 1000.30), (9, 1.20)]: cell = self.sheet.cell(row, 1) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_NUMBER) self.assertAlmostEqual(cell.value, time, places=2) self.assertTrue(cell.xf_index > 0) def test_get_from_merged_cell(self): sheet = self.book.sheet_by_name(u('ÖÄÜ')) cell = sheet.cell(2, 2) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_TEXT) self.assertEqual(cell.value, 'MERGED CELLS') self.assertTrue(cell.xf_index > 0) def test_ignore_diagram(self): sheet = self.book.sheet_by_name(u('Blätt3')) cell = sheet.cell(0, 0) self.assertEqual(cell.ctype, xlrd.book.XL_CELL_NUMBER) self.assertEqual(cell.value, 100) self.assertTrue(cell.xf_index > 0) xlrd-2.0.1/tests/test_formulas.py000066400000000000000000000041751376464300000171000ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Portions Copyright (C) 2010, Manfred Moitzi under a BSD licence from unittest import TestCase import xlrd from .helpers import from_sample try: ascii except NameError: # For Python 2 def ascii(s): a = repr(s) if a.startswith(('u"', "u'")): a = a[1:] return a class TestFormulas(TestCase): def setUp(self): book = xlrd.open_workbook(from_sample('formula_test_sjmachin.xls')) self.sheet = book.sheet_by_index(0) def get_value(self, col, row): return ascii(self.sheet.col_values(col)[row]) def test_cell_B2(self): self.assertEqual( self.get_value(1, 1), r"'\u041c\u041e\u0421\u041a\u0412\u0410 \u041c\u043e\u0441\u043a\u0432\u0430'", ) def test_cell_B3(self): self.assertEqual(self.get_value(1, 2), '0.14285714285714285') def test_cell_B4(self): self.assertEqual(self.get_value(1, 3), "'ABCDEF'") def test_cell_B5(self): self.assertEqual(self.get_value(1, 4), "''") def test_cell_B6(self): self.assertEqual(self.get_value(1, 5), '1') def test_cell_B7(self): self.assertEqual(self.get_value(1, 6), '7') def test_cell_B8(self): self.assertEqual( self.get_value(1, 7), r"'\u041c\u041e\u0421\u041a\u0412\u0410 \u041c\u043e\u0441\u043a\u0432\u0430'", ) class TestNameFormulas(TestCase): def setUp(self): book = xlrd.open_workbook(from_sample('formula_test_names.xls')) self.sheet = book.sheet_by_index(0) def get_value(self, col, row): return ascii(self.sheet.col_values(col)[row]) def test_unaryop(self): self.assertEqual(self.get_value(1, 1), '-7.0') def test_attrsum(self): self.assertEqual(self.get_value(1, 2), '4.0') def test_func(self): self.assertEqual(self.get_value(1, 3), '6.0') def test_func_var_args(self): self.assertEqual(self.get_value(1, 4), '3.0') def test_if(self): self.assertEqual(self.get_value(1, 5), "'b'") def test_choose(self): self.assertEqual(self.get_value(1, 6), "'C'") xlrd-2.0.1/tests/test_ignore_workbook_corruption_error.py000066400000000000000000000007031376464300000241360ustar00rootroot00000000000000from unittest import TestCase import xlrd from .helpers import from_sample class TestIgnoreWorkbookCorruption(TestCase): def test_not_corrupted(self): with self.assertRaises(Exception) as context: xlrd.open_workbook(from_sample('corrupted_error.xls')) self.assertTrue('Workbook corruption' in str(context.exception)) xlrd.open_workbook(from_sample('corrupted_error.xls'), ignore_workbook_corruption=True) xlrd-2.0.1/tests/test_inspect.py000066400000000000000000000012761376464300000167140ustar00rootroot00000000000000from xlrd import inspect_format from .helpers import from_sample def test_xlsx(): assert inspect_format(from_sample('sample.xlsx')) == 'xlsx' def test_xlsb(): assert inspect_format(from_sample('sample.xlsb')) == 'xlsb' def test_ods(): assert inspect_format(from_sample('sample.ods')) == 'ods' def test_zip(): assert inspect_format(from_sample('sample.zip')) == 'zip' def test_xls(): assert inspect_format(from_sample('namesdemo.xls')) == 'xls' def test_content(): with open(from_sample('sample.xlsx'), 'rb') as source: assert inspect_format(content=source.read()) == 'xlsx' def test_unknown(): assert inspect_format(from_sample('sample.txt')) is None xlrd-2.0.1/tests/test_missing_records.py000066400000000000000000000012221376464300000204300ustar00rootroot00000000000000from unittest import TestCase from xlrd import open_workbook from xlrd.biffh import XL_CELL_TEXT from .helpers import from_sample class TestMissingRecords(TestCase): def setUp(self): path = from_sample('biff4_no_format_no_window2.xls') self.book = open_workbook(path) self.sheet = self.book.sheet_by_index(0) def test_default_format(self): cell = self.sheet.cell(0, 0) self.assertEqual(cell.ctype, XL_CELL_TEXT) def test_default_window2_options(self): self.assertEqual(self.sheet.cached_page_break_preview_mag_factor, 0) self.assertEqual(self.sheet.cached_normal_view_mag_factor, 0) xlrd-2.0.1/tests/test_open_workbook.py000066400000000000000000000021371376464300000201220ustar00rootroot00000000000000import os import shutil import tempfile from unittest import TestCase import pytest from xlrd import open_workbook, XLRDError from .helpers import from_sample class TestOpen(object): # test different uses of open_workbook def test_names_demo(self): # For now, we just check this doesn't raise an error. open_workbook(from_sample('namesdemo.xls')) def test_ragged_rows_tidied_with_formatting(self): # For now, we just check this doesn't raise an error. open_workbook(from_sample('issue20.xls'), formatting_info=True) def test_BYTES_X00(self): # For now, we just check this doesn't raise an error. open_workbook(from_sample('picture_in_cell.xls'), formatting_info=True) def test_open_xlsx(self): with pytest.raises(XLRDError, match='Excel xlsx file; not supported'): open_workbook(from_sample('sample.xlsx')) def test_open_unknown(self): with pytest.raises(XLRDError, match="Unsupported format, or corrupt file"): open_workbook(from_sample('sample.txt')) xlrd-2.0.1/tests/test_sheet.py000066400000000000000000000120671376464300000163570ustar00rootroot00000000000000# Portions Copyright (C) 2010, Manfred Moitzi under a BSD licence import types from unittest import TestCase import xlrd from xlrd.timemachine import xrange from .helpers import from_sample SHEETINDEX = 0 NROWS = 15 NCOLS = 13 ROW_ERR = NROWS + 10 COL_ERR = NCOLS + 10 class TestSheet(TestCase): sheetnames = ['PROFILEDEF', 'AXISDEF', 'TRAVERSALCHAINAGE', 'AXISDATUMLEVELS', 'PROFILELEVELS'] def setUp(self): self.book = xlrd.open_workbook(from_sample('profiles.xls'), formatting_info=True) def check_sheet_function(self, function): self.assertTrue(function(0, 0)) self.assertTrue(function(NROWS-1, NCOLS-1)) def check_sheet_function_index_error(self, function): self.assertRaises(IndexError, function, ROW_ERR, 0) self.assertRaises(IndexError, function, 0, COL_ERR) def check_col_slice(self, col_function): _slice = col_function(0, 2, NROWS-2) self.assertEqual(len(_slice), NROWS-4) def check_row_slice(self, row_function): _slice = row_function(0, 2, NCOLS-2) self.assertEqual(len(_slice), NCOLS-4) def test_nrows(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.assertEqual(sheet.nrows, NROWS) def test_ncols(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.assertEqual(sheet.ncols, NCOLS) def test_cell(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.assertNotEqual(xlrd.empty_cell, sheet.cell(0, 0)) self.assertNotEqual(xlrd.empty_cell, sheet.cell(NROWS-1, NCOLS-1)) def test_cell_error(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function_index_error(sheet.cell) def test_cell_type(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function(sheet.cell_type) def test_cell_type_error(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function_index_error(sheet.cell_type) def test_cell_value(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function(sheet.cell_value) def test_cell_value_error(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function_index_error(sheet.cell_value) def test_cell_xf_index(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function(sheet.cell_xf_index) def test_cell_xf_index_error(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_sheet_function_index_error(sheet.cell_xf_index) def test_col(self): sheet = self.book.sheet_by_index(SHEETINDEX) col = sheet.col(0) self.assertEqual(len(col), NROWS) def test_row(self): sheet = self.book.sheet_by_index(SHEETINDEX) row = sheet.row(0) self.assertEqual(len(row), NCOLS) def test_getitem_int(self): sheet = self.book.sheet_by_index(SHEETINDEX) row = sheet[0] self.assertEqual(len(row), NCOLS) def test_getitem_tuple(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.assertNotEqual(xlrd.empty_cell, sheet[0, 0]) self.assertNotEqual(xlrd.empty_cell, sheet[NROWS-1, NCOLS-1]) def test_getitem_failure(self): sheet = self.book.sheet_by_index(SHEETINDEX) with self.assertRaises(ValueError): sheet[0, 0, 0] with self.assertRaises(TypeError): sheet["hi"] def test_get_rows(self): sheet = self.book.sheet_by_index(SHEETINDEX) rows = sheet.get_rows() self.assertTrue(isinstance(rows, types.GeneratorType), True) self.assertEqual(len(list(rows)), sheet.nrows) def test_iter(self): sheet = self.book.sheet_by_index(SHEETINDEX) rows = [] # check syntax for row in sheet: rows.append(row) self.assertEqual(len(rows), sheet.nrows) def test_col_slice(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_col_slice(sheet.col_slice) def test_col_types(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_col_slice(sheet.col_types) def test_col_values(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_col_slice(sheet.col_values) def test_row_slice(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_row_slice(sheet.row_slice) def test_row_types(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_row_slice(sheet.col_types) def test_row_values(self): sheet = self.book.sheet_by_index(SHEETINDEX) self.check_col_slice(sheet.row_values) class TestSheetRagged(TestCase): def test_read_ragged(self): book = xlrd.open_workbook(from_sample('ragged.xls'), ragged_rows=True) sheet = book.sheet_by_index(0) self.assertEqual(sheet.row_len(0), 3) self.assertEqual(sheet.row_len(1), 2) self.assertEqual(sheet.row_len(2), 1) self.assertEqual(sheet.row_len(3), 4) self.assertEqual(sheet.row_len(4), 4) xlrd-2.0.1/tests/test_workbook.py000066400000000000000000000037511376464300000171040ustar00rootroot00000000000000# Portions Copyright (C) 2010, Manfred Moitzi under a BSD licence from unittest import TestCase import xlrd from xlrd import open_workbook from xlrd.book import Book from xlrd.sheet import Sheet from .helpers import from_sample SHEETINDEX = 0 NROWS = 15 NCOLS = 13 class TestWorkbook(TestCase): sheetnames = ['PROFILEDEF', 'AXISDEF', 'TRAVERSALCHAINAGE', 'AXISDATUMLEVELS', 'PROFILELEVELS'] def setUp(self): self.book = open_workbook(from_sample('profiles.xls')) def test_open_workbook(self): self.assertTrue(isinstance(self.book, Book)) def test_nsheets(self): self.assertEqual(self.book.nsheets, 5) def test_sheet_by_name(self): for name in self.sheetnames: sheet = self.book.sheet_by_name(name) self.assertTrue(isinstance(sheet, Sheet)) self.assertEqual(name, sheet.name) def test_sheet_by_index(self): for index in range(5): sheet = self.book.sheet_by_index(index) self.assertTrue(isinstance(sheet, Sheet)) self.assertEqual(sheet.name, self.sheetnames[index]) def test_sheets(self): sheets = self.book.sheets() for index, sheet in enumerate(sheets): self.assertTrue(isinstance(sheet, Sheet)) self.assertEqual(sheet.name, self.sheetnames[index]) def test_sheet_names(self): self.assertEqual(self.sheetnames, self.book.sheet_names()) def test_getitem_ix(self): sheet = self.book[SHEETINDEX] self.assertNotEqual(xlrd.empty_cell, sheet.cell(0, 0)) self.assertNotEqual(xlrd.empty_cell, sheet.cell(NROWS - 1, NCOLS - 1)) def test_getitem_name(self): sheet = self.book[self.sheetnames[SHEETINDEX]] self.assertNotEqual(xlrd.empty_cell, sheet.cell(0, 0)) self.assertNotEqual(xlrd.empty_cell, sheet.cell(NROWS - 1, NCOLS - 1)) def test_iter(self): sheets = [sh.name for sh in self.book] self.assertEqual(sheets, self.sheetnames) xlrd-2.0.1/tests/test_xldate.py000066400000000000000000000044141376464300000165250ustar00rootroot00000000000000#!/usr/bin/env python # Author: mozman # Purpose: test xldate.py # Created: 04.12.2010 # Copyright (C) 2010, Manfred Moitzi # License: BSD licence import unittest from xlrd import xldate DATEMODE = 0 # 1900-based class TestXLDate(unittest.TestCase): def test_date_as_tuple(self): date = xldate.xldate_as_tuple(2741., DATEMODE) self.assertEqual(date, (1907, 7, 3, 0, 0, 0)) date = xldate.xldate_as_tuple(38406., DATEMODE) self.assertEqual(date, (2005, 2, 23, 0, 0, 0)) date = xldate.xldate_as_tuple(32266., DATEMODE) self.assertEqual(date, (1988, 5, 3, 0, 0, 0)) def test_time_as_tuple(self): time = xldate.xldate_as_tuple(.273611, DATEMODE) self.assertEqual(time, (0, 0, 0, 6, 34, 0)) time = xldate.xldate_as_tuple(.538889, DATEMODE) self.assertEqual(time, (0, 0, 0, 12, 56, 0)) time = xldate.xldate_as_tuple(.741123, DATEMODE) self.assertEqual(time, (0, 0, 0, 17, 47, 13)) def test_xldate_from_date_tuple(self): date = xldate.xldate_from_date_tuple( (1907, 7, 3), DATEMODE ) self.assertAlmostEqual(date, 2741.) date = xldate.xldate_from_date_tuple( (2005, 2, 23), DATEMODE ) self.assertAlmostEqual(date, 38406.) date = xldate.xldate_from_date_tuple( (1988, 5, 3), DATEMODE ) self.assertAlmostEqual(date, 32266.) def test_xldate_from_time_tuple(self): time = xldate.xldate_from_time_tuple( (6, 34, 0) ) self.assertAlmostEqual(time, .273611, places=6) time = xldate.xldate_from_time_tuple( (12, 56, 0) ) self.assertAlmostEqual(time, .538889, places=6) time = xldate.xldate_from_time_tuple( (17, 47, 13) ) self.assertAlmostEqual(time, .741123, places=6) def test_xldate_from_datetime_tuple(self): date = xldate.xldate_from_datetime_tuple( (1907, 7, 3, 6, 34, 0), DATEMODE) self.assertAlmostEqual(date, 2741.273611, places=6) date = xldate.xldate_from_datetime_tuple( (2005, 2, 23, 12, 56, 0), DATEMODE) self.assertAlmostEqual(date, 38406.538889, places=6) date = xldate.xldate_from_datetime_tuple( (1988, 5, 3, 17, 47, 13), DATEMODE) self.assertAlmostEqual(date, 32266.741123, places=6) if __name__=='__main__': unittest.main() xlrd-2.0.1/tests/test_xldate_to_datetime.py000066400000000000000000000137611376464300000211100ustar00rootroot00000000000000############################################################################### # # Tests for the xlrd xldate.xldate_as_datetime() function. # import unittest from datetime import datetime from xlrd import xldate not_1904 = False is_1904 = True class TestConvertToDateTime(unittest.TestCase): """ Testcases to test the _xldate_to_datetime() function against dates extracted from Excel files, with 1900/1904 epochs. """ def test_dates_and_times_1900_epoch(self): """ Test the _xldate_to_datetime() function for dates and times in the Excel standard 1900 epoch. """ # Test Excel dates strings and corresponding serial date numbers taken # from an Excel file. excel_dates = [ # Excel's 0.0 date in the 1900 epoch is 1 day before 1900. ('1899-12-31T00:00:00.000', 0), # Date/time before the false Excel 1900 leapday. ('1900-02-28T02:11:11.986', 59.09111094906), # Date/time after the false Excel 1900 leapday. ('1900-03-01T05:46:44.068', 61.24078782403), # Random date/times in Excel's 0-9999.9999+ range. ('1982-08-25T00:15:20.213', 30188.010650613425), ('2065-04-19T00:16:48.290', 60376.011670023145), ('3222-06-11T03:08:08.251', 483014.13065105322), ('4379-08-03T06:14:48.580', 905652.26028449077), ('5949-12-30T12:59:54.263', 1479232.5416002662), # End of Excel's date range. ('9999-12-31T23:59:59.000', 2958465.999988426), ] # Convert the Excel date strings to datetime objects and compare # against the dateitme return value of xldate.xldate_as_datetime(). for excel_date in excel_dates: exp = datetime.strptime(excel_date[0], "%Y-%m-%dT%H:%M:%S.%f") got = xldate.xldate_as_datetime(excel_date[1], not_1904) self.assertEqual(got, exp) def test_dates_only_1900_epoch(self): """ Test the _xldate_to_datetime() function for dates in the Excel standard 1900 epoch. """ # Test Excel dates strings and corresponding serial date numbers taken # from an Excel file. excel_dates = [ # Excel's day 0 in the 1900 epoch is 1 day before 1900. ('1899-12-31', 0), # Excel's day 1 in the 1900 epoch. ('1900-01-01', 1), # Date/time before the false Excel 1900 leapday. ('1900-02-28', 59), # Date/time after the false Excel 1900 leapday. ('1900-03-01', 61), # Random date/times in Excel's 0-9999.9999+ range. ('1902-09-27', 1001), ('1999-12-31', 36525), ('2000-01-01', 36526), ('4000-12-31', 767376), ('4321-01-01', 884254), ('9999-01-01', 2958101), # End of Excel's date range. ('9999-12-31', 2958465), ] # Convert the Excel date strings to datetime objects and compare # against the dateitme return value of xldate.xldate_as_datetime(). for excel_date in excel_dates: exp = datetime.strptime(excel_date[0], "%Y-%m-%d") got = xldate.xldate_as_datetime(excel_date[1], not_1904) self.assertEqual(got, exp) def test_dates_only_1904_epoch(self): """ Test the _xldate_to_datetime() function for dates in the Excel Mac/1904 epoch. """ # Test Excel dates strings and corresponding serial date numbers taken # from an Excel file. excel_dates = [ # Excel's day 0 in the 1904 epoch. ('1904-01-01', 0), # Random date/times in Excel's 0-9999.9999+ range. ('1904-01-31', 30), ('1904-08-31', 243), ('1999-02-28', 34757), ('1999-12-31', 35063), ('2000-01-01', 35064), ('2400-12-31', 181526), ('4000-01-01', 765549), ('9999-01-01', 2956639), # End of Excel's date range. ('9999-12-31', 2957003), ] # Convert the Excel date strings to datetime objects and compare # against the dateitme return value of xldate.xldate_as_datetime(). for excel_date in excel_dates: exp = datetime.strptime(excel_date[0], "%Y-%m-%d") got = xldate.xldate_as_datetime(excel_date[1], is_1904) self.assertEqual(got, exp) def test_times_only(self): """ Test the _xldate_to_datetime() function for times only, i.e, the fractional part of the Excel date when the serial date is 0. """ # Test Excel dates strings and corresponding serial date numbers taken # from an Excel file. The 1899-12-31 date is Excel's day 0. excel_dates = [ # Random times in Excel's 0-0.9999+ range for 1 day. ('1899-12-31T00:00:00.000', 0), ('1899-12-31T00:15:20.213', 1.0650613425925924E-2), ('1899-12-31T02:24:37.095', 0.10042934027777778), ('1899-12-31T04:56:35.792', 0.2059698148148148), ('1899-12-31T07:31:20.407', 0.31343063657407405), ('1899-12-31T09:37:23.945', 0.40097158564814817), ('1899-12-31T12:09:48.602', 0.50681252314814818), ('1899-12-31T14:37:57.451', 0.60969271990740748), ('1899-12-31T17:04:02.415', 0.71113906250000003), ('1899-12-31T19:14:24.673', 0.80167445601851861), ('1899-12-31T21:39:05.944', 0.90215212962962965), ('1899-12-31T23:17:12.632', 0.97028509259259266), ('1899-12-31T23:59:59.999', 0.99999998842592586), ] # Convert the Excel date strings to datetime objects and compare # against the dateitme return value of xldate.xldate_as_datetime(). for excel_date in excel_dates: exp = datetime.strptime(excel_date[0], "%Y-%m-%dT%H:%M:%S.%f") got = xldate.xldate_as_datetime(excel_date[1], not_1904) self.assertEqual(got, exp) xlrd-2.0.1/xlrd/000077500000000000000000000000001376464300000134375ustar00rootroot00000000000000xlrd-2.0.1/xlrd/__init__.py000066400000000000000000000162301376464300000155520ustar00rootroot00000000000000# Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. import os import pprint import sys import zipfile from . import timemachine from .biffh import ( XL_CELL_BLANK, XL_CELL_BOOLEAN, XL_CELL_DATE, XL_CELL_EMPTY, XL_CELL_ERROR, XL_CELL_NUMBER, XL_CELL_TEXT, XLRDError, biff_text_from_num, error_text_from_code, ) from .book import Book, colname, open_workbook_xls from .compdoc import SIGNATURE as XLS_SIGNATURE from .formula import * # is constrained by __all__ from .info import __VERSION__, __version__ from .sheet import empty_cell from .xldate import XLDateError, xldate_as_datetime, xldate_as_tuple #: descriptions of the file types :mod:`xlrd` can :func:`inspect `. FILE_FORMAT_DESCRIPTIONS = { 'xls': 'Excel xls', 'xlsb': 'Excel 2007 xlsb file', 'xlsx': 'Excel xlsx file', 'ods': 'Openoffice.org ODS file', 'zip': 'Unknown ZIP file', None: 'Unknown file type', } ZIP_SIGNATURE = b"PK\x03\x04" PEEK_SIZE = max(len(XLS_SIGNATURE), len(ZIP_SIGNATURE)) def inspect_format(path=None, content=None): """ Inspect the content at the supplied path or the :class:`bytes` content provided and return the file's type as a :class:`str`, or ``None`` if it cannot be determined. :param path: A :class:`string ` path containing the content to inspect. ``~`` will be expanded. :param content: The :class:`bytes` content to inspect. :returns: A :class:`str`, or ``None`` if the format cannot be determined. The return value can always be looked up in :data:`FILE_FORMAT_DESCRIPTIONS` to return a human-readable description of the format found. """ if content: peek = content[:PEEK_SIZE] else: path = os.path.expanduser(path) with open(path, "rb") as f: peek = f.read(PEEK_SIZE) if peek.startswith(XLS_SIGNATURE): return 'xls' if peek.startswith(ZIP_SIGNATURE): zf = zipfile.ZipFile(timemachine.BYTES_IO(content) if content else path) # Workaround for some third party files that use forward slashes and # lower case names. We map the expected name in lowercase to the # actual filename in the zip container. component_names = {name.replace('\\', '/').lower(): name for name in zf.namelist()} if 'xl/workbook.xml' in component_names: return 'xlsx' if 'xl/workbook.bin' in component_names: return 'xlsb' if 'content.xml' in component_names: return 'ods' return 'zip' def open_workbook(filename=None, logfile=sys.stdout, verbosity=0, use_mmap=True, file_contents=None, encoding_override=None, formatting_info=False, on_demand=False, ragged_rows=False, ignore_workbook_corruption=False ): """ Open a spreadsheet file for data extraction. :param filename: The path to the spreadsheet file to be opened. :param logfile: An open file to which messages and diagnostics are written. :param verbosity: Increases the volume of trace material written to the logfile. :param use_mmap: Whether to use the mmap module is determined heuristically. Use this arg to override the result. Current heuristic: mmap is used if it exists. :param file_contents: A string or an :class:`mmap.mmap` object or some other behave-alike object. If ``file_contents`` is supplied, ``filename`` will not be used, except (possibly) in messages. :param encoding_override: Used to overcome missing or bad codepage information in older-version files. See :doc:`unicode`. :param formatting_info: The default is ``False``, which saves memory. In this case, "Blank" cells, which are those with their own formatting information but no data, are treated as empty by ignoring the file's ``BLANK`` and ``MULBLANK`` records. This cuts off any bottom or right "margin" of rows of empty or blank cells. Only :meth:`~xlrd.sheet.Sheet.cell_value` and :meth:`~xlrd.sheet.Sheet.cell_type` are available. When ``True``, formatting information will be read from the spreadsheet file. This provides all cells, including empty and blank cells. Formatting information is available for each cell. Note that this will raise a NotImplementedError when used with an xlsx file. :param on_demand: Governs whether sheets are all loaded initially or when demanded by the caller. See :doc:`on_demand`. :param ragged_rows: The default of ``False`` means all rows are padded out with empty cells so that all rows have the same size as found in :attr:`~xlrd.sheet.Sheet.ncols`. ``True`` means that there are no empty cells at the ends of rows. This can result in substantial memory savings if rows are of widely varying sizes. See also the :meth:`~xlrd.sheet.Sheet.row_len` method. :param ignore_workbook_corruption: This option allows to read corrupted workbooks. When ``False`` you may face CompDocError: Workbook corruption. When ``True`` that exception will be ignored. :returns: An instance of the :class:`~xlrd.book.Book` class. """ file_format = inspect_format(filename, file_contents) # We have to let unknown file formats pass through here, as some ancient # files that xlrd can parse don't start with the expected signature. if file_format and file_format != 'xls': raise XLRDError(FILE_FORMAT_DESCRIPTIONS[file_format]+'; not supported') bk = open_workbook_xls( filename=filename, logfile=logfile, verbosity=verbosity, use_mmap=use_mmap, file_contents=file_contents, encoding_override=encoding_override, formatting_info=formatting_info, on_demand=on_demand, ragged_rows=ragged_rows, ignore_workbook_corruption=ignore_workbook_corruption, ) return bk def dump(filename, outfile=sys.stdout, unnumbered=False): """ For debugging: dump an XLS file's BIFF records in char & hex. :param filename: The path to the file to be dumped. :param outfile: An open file, to which the dump is written. :param unnumbered: If true, omit offsets (for meaningful diffs). """ from .biffh import biff_dump bk = Book() bk.biff2_8_load(filename=filename, logfile=outfile, ) biff_dump(bk.mem, bk.base, bk.stream_len, 0, outfile, unnumbered) def count_records(filename, outfile=sys.stdout): """ For debugging and analysis: summarise the file's BIFF records. ie: produce a sorted file of ``(record_name, count)``. :param filename: The path to the file to be summarised. :param outfile: An open file, to which the summary is written. """ from .biffh import biff_count_records bk = Book() bk.biff2_8_load(filename=filename, logfile=outfile, ) biff_count_records(bk.mem, bk.base, bk.stream_len, outfile) xlrd-2.0.1/xlrd/biffh.py000066400000000000000000000404131376464300000150710ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Portions copyright © 2005-2010 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. from __future__ import print_function import sys from struct import unpack from .timemachine import * DEBUG = 0 class XLRDError(Exception): """ An exception indicating problems reading data from an Excel file. """ class BaseObject(object): """ Parent of almost all other classes in the package. Defines a common :meth:`dump` method for debugging. """ _repr_these = [] def dump(self, f=None, header=None, footer=None, indent=0): """ :param f: open file object, to which the dump is written :param header: text to write before the dump :param footer: text to write after the dump :param indent: number of leading spaces (for recursive calls) """ if f is None: f = sys.stderr if hasattr(self, "__slots__"): alist = [] for attr in self.__slots__: alist.append((attr, getattr(self, attr))) else: alist = self.__dict__.items() alist = sorted(alist) pad = " " * indent if header is not None: print(header, file=f) list_type = type([]) dict_type = type({}) for attr, value in alist: if getattr(value, 'dump', None) and attr != 'book': value.dump(f, header="%s%s (%s object):" % (pad, attr, value.__class__.__name__), indent=indent+4) elif (attr not in self._repr_these and (isinstance(value, list_type) or isinstance(value, dict_type))): print("%s%s: %s, len = %d" % (pad, attr, type(value), len(value)), file=f) else: fprintf(f, "%s%s: %r\n", pad, attr, value) if footer is not None: print(footer, file=f) FUN, FDT, FNU, FGE, FTX = range(5) # unknown, date, number, general, text DATEFORMAT = FDT NUMBERFORMAT = FNU ( XL_CELL_EMPTY, XL_CELL_TEXT, XL_CELL_NUMBER, XL_CELL_DATE, XL_CELL_BOOLEAN, XL_CELL_ERROR, XL_CELL_BLANK, # for use in debugging, gathering stats, etc ) = range(7) biff_text_from_num = { 0: "(not BIFF)", 20: "2.0", 21: "2.1", 30: "3", 40: "4S", 45: "4W", 50: "5", 70: "7", 80: "8", 85: "8X", } #: This dictionary can be used to produce a text version of the internal codes #: that Excel uses for error cells. error_text_from_code = { 0x00: '#NULL!', # Intersection of two cell ranges is empty 0x07: '#DIV/0!', # Division by zero 0x0F: '#VALUE!', # Wrong type of operand 0x17: '#REF!', # Illegal or deleted cell reference 0x1D: '#NAME?', # Wrong function or range name 0x24: '#NUM!', # Value range overflow 0x2A: '#N/A', # Argument or function not available } BIFF_FIRST_UNICODE = 80 XL_WORKBOOK_GLOBALS = WBKBLOBAL = 0x5 XL_WORKBOOK_GLOBALS_4W = 0x100 XL_WORKSHEET = WRKSHEET = 0x10 XL_BOUNDSHEET_WORKSHEET = 0x00 XL_BOUNDSHEET_CHART = 0x02 XL_BOUNDSHEET_VB_MODULE = 0x06 # XL_RK2 = 0x7e XL_ARRAY = 0x0221 XL_ARRAY2 = 0x0021 XL_BLANK = 0x0201 XL_BLANK_B2 = 0x01 XL_BOF = 0x809 XL_BOOLERR = 0x205 XL_BOOLERR_B2 = 0x5 XL_BOUNDSHEET = 0x85 XL_BUILTINFMTCOUNT = 0x56 XL_CF = 0x01B1 XL_CODEPAGE = 0x42 XL_COLINFO = 0x7D XL_COLUMNDEFAULT = 0x20 # BIFF2 only XL_COLWIDTH = 0x24 # BIFF2 only XL_CONDFMT = 0x01B0 XL_CONTINUE = 0x3c XL_COUNTRY = 0x8C XL_DATEMODE = 0x22 XL_DEFAULTROWHEIGHT = 0x0225 XL_DEFCOLWIDTH = 0x55 XL_DIMENSION = 0x200 XL_DIMENSION2 = 0x0 XL_EFONT = 0x45 XL_EOF = 0x0a XL_EXTERNNAME = 0x23 XL_EXTERNSHEET = 0x17 XL_EXTSST = 0xff XL_FEAT11 = 0x872 XL_FILEPASS = 0x2f XL_FONT = 0x31 XL_FONT_B3B4 = 0x231 XL_FORMAT = 0x41e XL_FORMAT2 = 0x1E # BIFF2, BIFF3 XL_FORMULA = 0x6 XL_FORMULA3 = 0x206 XL_FORMULA4 = 0x406 XL_GCW = 0xab XL_HLINK = 0x01B8 XL_QUICKTIP = 0x0800 XL_HORIZONTALPAGEBREAKS = 0x1b XL_INDEX = 0x20b XL_INTEGER = 0x2 # BIFF2 only XL_IXFE = 0x44 # BIFF2 only XL_LABEL = 0x204 XL_LABEL_B2 = 0x04 XL_LABELRANGES = 0x15f XL_LABELSST = 0xfd XL_LEFTMARGIN = 0x26 XL_TOPMARGIN = 0x28 XL_RIGHTMARGIN = 0x27 XL_BOTTOMMARGIN = 0x29 XL_HEADER = 0x14 XL_FOOTER = 0x15 XL_HCENTER = 0x83 XL_VCENTER = 0x84 XL_MERGEDCELLS = 0xE5 XL_MSO_DRAWING = 0x00EC XL_MSO_DRAWING_GROUP = 0x00EB XL_MSO_DRAWING_SELECTION = 0x00ED XL_MULRK = 0xbd XL_MULBLANK = 0xbe XL_NAME = 0x18 XL_NOTE = 0x1c XL_NUMBER = 0x203 XL_NUMBER_B2 = 0x3 XL_OBJ = 0x5D XL_PAGESETUP = 0xA1 XL_PALETTE = 0x92 XL_PANE = 0x41 XL_PRINTGRIDLINES = 0x2B XL_PRINTHEADERS = 0x2A XL_RK = 0x27e XL_ROW = 0x208 XL_ROW_B2 = 0x08 XL_RSTRING = 0xd6 XL_SCL = 0x00A0 XL_SHEETHDR = 0x8F # BIFF4W only XL_SHEETPR = 0x81 XL_SHEETSOFFSET = 0x8E # BIFF4W only XL_SHRFMLA = 0x04bc XL_SST = 0xfc XL_STANDARDWIDTH = 0x99 XL_STRING = 0x207 XL_STRING_B2 = 0x7 XL_STYLE = 0x293 XL_SUPBOOK = 0x1AE # aka EXTERNALBOOK in OOo docs XL_TABLEOP = 0x236 XL_TABLEOP2 = 0x37 XL_TABLEOP_B2 = 0x36 XL_TXO = 0x1b6 XL_UNCALCED = 0x5e XL_UNKNOWN = 0xffff XL_VERTICALPAGEBREAKS = 0x1a XL_WINDOW2 = 0x023E XL_WINDOW2_B2 = 0x003E XL_WRITEACCESS = 0x5C XL_WSBOOL = XL_SHEETPR XL_XF = 0xe0 XL_XF2 = 0x0043 # BIFF2 version of XF record XL_XF3 = 0x0243 # BIFF3 version of XF record XL_XF4 = 0x0443 # BIFF4 version of XF record boflen = {0x0809: 8, 0x0409: 6, 0x0209: 6, 0x0009: 4} bofcodes = (0x0809, 0x0409, 0x0209, 0x0009) XL_FORMULA_OPCODES = (0x0006, 0x0406, 0x0206) _cell_opcode_list = [ XL_BOOLERR, XL_FORMULA, XL_FORMULA3, XL_FORMULA4, XL_LABEL, XL_LABELSST, XL_MULRK, XL_NUMBER, XL_RK, XL_RSTRING, ] _cell_opcode_dict = {} for _cell_opcode in _cell_opcode_list: _cell_opcode_dict[_cell_opcode] = 1 def is_cell_opcode(c): return c in _cell_opcode_dict def upkbits(tgt_obj, src, manifest, local_setattr=setattr): for n, mask, attr in manifest: local_setattr(tgt_obj, attr, (src & mask) >> n) def upkbitsL(tgt_obj, src, manifest, local_setattr=setattr, local_int=int): for n, mask, attr in manifest: local_setattr(tgt_obj, attr, local_int((src & mask) >> n)) def unpack_string(data, pos, encoding, lenlen=1): nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0] pos += lenlen return unicode(data[pos:pos+nchars], encoding) def unpack_string_update_pos(data, pos, encoding, lenlen=1, known_len=None): if known_len is not None: # On a NAME record, the length byte is detached from the front of the string. nchars = known_len else: nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0] pos += lenlen newpos = pos + nchars return (unicode(data[pos:newpos], encoding), newpos) def unpack_unicode(data, pos, lenlen=2): "Return unicode_strg" nchars = unpack('<' + 'BH'[lenlen-1], data[pos:pos+lenlen])[0] if not nchars: # Ambiguous whether 0-length string should have an "options" byte. # Avoid crash if missing. return UNICODE_LITERAL("") pos += lenlen options = BYTES_ORD(data[pos]) pos += 1 # phonetic = options & 0x04 # richtext = options & 0x08 if options & 0x08: # rt = unpack(' endpos=%d pos=%d endsub=%d substrg=%r\n', ofs, dlen, base, endpos, pos, endsub, substrg) break hexd = ''.join("%02x " % BYTES_ORD(c) for c in substrg) chard = '' for c in substrg: c = chr(BYTES_ORD(c)) if c == '\0': c = '~' elif not (' ' <= c <= '~'): c = '?' chard += c if numbered: num_prefix = "%5d: " % (base+pos-ofs) fprintf(fout, "%s %-48s %s\n", num_prefix, hexd, chard) pos = endsub def biff_dump(mem, stream_offset, stream_len, base=0, fout=sys.stdout, unnumbered=False): pos = stream_offset stream_end = stream_offset + stream_len adj = base - stream_offset dummies = 0 numbered = not unnumbered num_prefix = '' while stream_end - pos >= 4: rc, length = unpack('') if numbered: num_prefix = "%5d: " % (adj + pos) fprintf(fout, "%s%04x %s len = %04x (%d)\n", num_prefix, rc, recname, length, length) pos += 4 hex_char_dump(mem, pos, length, adj+pos, fout, unnumbered) pos += length if dummies: if numbered: num_prefix = "%5d: " % (adj + savpos) fprintf(fout, "%s---- %d zero bytes skipped ----\n", num_prefix, dummies) if pos < stream_end: if numbered: num_prefix = "%5d: " % (adj + pos) fprintf(fout, "%s---- Misc bytes at end ----\n", num_prefix) hex_char_dump(mem, pos, stream_end-pos, adj + pos, fout, unnumbered) elif pos > stream_end: fprintf(fout, "Last dumped record has length (%d) that is too large\n", length) def biff_count_records(mem, stream_offset, stream_len, fout=sys.stdout): pos = stream_offset stream_end = stream_offset + stream_len tally = {} while stream_end - pos >= 4: rc, length = unpack(' 1: fprintf( bk.logfile, "*** WARNING: Excel 4.0 workbook (.XLW) file contains %d worksheets.\n" "*** Book-level data will be that of the last worksheet.\n", bk.nsheets ) t2 = perf_counter() bk.load_time_stage_2 = t2 - t1 except: bk.release_resources() raise # normal exit if not on_demand: bk.release_resources() return bk class Name(BaseObject): """ Information relating to a named reference, formula, macro, etc. .. note:: Name information is **not** extracted from files older than Excel 5.0 (``Book.biff_version < 50``) """ _repr_these = ['stack'] book = None # parent #: 0 = Visible; 1 = Hidden hidden = 0 #: 0 = Command macro; 1 = Function macro. Relevant only if macro == 1 func = 0 #: 0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1 vbasic = 0 #: 0 = Standard name; 1 = Macro name macro = 0 #: 0 = Simple formula; 1 = Complex formula (array formula or user defined). #: #: .. note:: No examples have been sighted. complex = 0 #: 0 = User-defined name; 1 = Built-in name #: #: Common examples: ``Print_Area``, ``Print_Titles``; see OOo docs for #: full list builtin = 0 #: Function group. Relevant only if macro == 1; see OOo docs for values. funcgroup = 0 #: 0 = Formula definition; 1 = Binary data #: #: .. note:: No examples have been sighted. binary = 0 #: The index of this object in book.name_obj_list name_index = 0 # A Unicode string. If builtin, decoded as per OOo docs. name = UNICODE_LITERAL("") #: An 8-bit string. raw_formula = b'' #: ``-1``: #: The name is global (visible in all calculation sheets). #: ``-2``: #: The name belongs to a macro sheet or VBA sheet. #: ``-3``: #: The name is invalid. #: ``0 <= scope < book.nsheets``: #: The name is local to the sheet whose index is scope. scope = -1 #: The result of evaluating the formula, if any. #: If no formula, or evaluation of the formula encountered problems, #: the result is ``None``. Otherwise the result is a single instance of the #: :class:`~xlrd.formula.Operand` class. # result = None def cell(self): """ This is a convenience method for the frequent use case where the name refers to a single cell. :returns: An instance of the :class:`~xlrd.sheet.Cell` class. :raises xlrd.biffh.XLRDError: The name is not a constant absolute reference to a single cell. """ res = self.result if res: # result should be an instance of the Operand class kind = res.kind value = res.value if kind == oREF and len(value) == 1: ref3d = value[0] if (0 <= ref3d.shtxlo == ref3d.shtxhi - 1 and ref3d.rowxlo == ref3d.rowxhi - 1 and ref3d.colxlo == ref3d.colxhi - 1): sh = self.book.sheet_by_index(ref3d.shtxlo) return sh.cell(ref3d.rowxlo, ref3d.colxlo) self.dump( self.book.logfile, header="=== Dump of Name object ===", footer="======= End of dump =======", ) raise XLRDError("Not a constant absolute reference to a single cell") def area2d(self, clipped=True): """ This is a convenience method for the use case where the name refers to one rectangular area in one worksheet. :param clipped: If ``True``, the default, the returned rectangle is clipped to fit in ``(0, sheet.nrows, 0, sheet.ncols)``. it is guaranteed that ``0 <= rowxlo <= rowxhi <= sheet.nrows`` and that the number of usable rows in the area (which may be zero) is ``rowxhi - rowxlo``; likewise for columns. :returns: a tuple ``(sheet_object, rowxlo, rowxhi, colxlo, colxhi)``. :raises xlrd.biffh.XLRDError: The name is not a constant absolute reference to a single area in a single sheet. """ res = self.result if res: # result should be an instance of the Operand class kind = res.kind value = res.value if kind == oREF and len(value) == 1: # only 1 reference ref3d = value[0] if 0 <= ref3d.shtxlo == ref3d.shtxhi - 1: # only 1 usable sheet sh = self.book.sheet_by_index(ref3d.shtxlo) if not clipped: return sh, ref3d.rowxlo, ref3d.rowxhi, ref3d.colxlo, ref3d.colxhi rowxlo = min(ref3d.rowxlo, sh.nrows) rowxhi = max(rowxlo, min(ref3d.rowxhi, sh.nrows)) colxlo = min(ref3d.colxlo, sh.ncols) colxhi = max(colxlo, min(ref3d.colxhi, sh.ncols)) assert 0 <= rowxlo <= rowxhi <= sh.nrows assert 0 <= colxlo <= colxhi <= sh.ncols return sh, rowxlo, rowxhi, colxlo, colxhi self.dump( self.book.logfile, header="=== Dump of Name object ===", footer="======= End of dump =======", ) raise XLRDError("Not a constant absolute reference to a single area in a single sheet") class Book(BaseObject): """ Contents of a "workbook". .. warning:: You should not instantiate this class yourself. You use the :class:`Book` object that was returned when you called :func:`~xlrd.open_workbook`. """ #: The number of worksheets present in the workbook file. #: This information is available even when no sheets have yet been loaded. nsheets = 0 #: Which date system was in force when this file was last saved. #: #: 0: #: 1900 system (the Excel for Windows default). #: #: 1: #: 1904 system (the Excel for Macintosh default). #: #: Defaults to 0 in case it's not specified in the file. datemode = 0 #: Version of BIFF (Binary Interchange File Format) used to create the file. #: Latest is 8.0 (represented here as 80), introduced with Excel 97. #: Earliest supported by this module: 2.0 (represented as 20). biff_version = 0 #: List containing a :class:`Name` object for each ``NAME`` record in the #: workbook. #: #: .. versionadded:: 0.6.0 name_obj_list = [] #: An integer denoting the character set used for strings in this file. #: For BIFF 8 and later, this will be 1200, meaning Unicode; #: more precisely, UTF_16_LE. #: For earlier versions, this is used to derive the appropriate Python #: encoding to be used to convert to Unicode. #: Examples: ``1252 -> 'cp1252'``, ``10000 -> 'mac_roman'`` codepage = None #: The encoding that was derived from the codepage. encoding = None #: A tuple containing the telephone country code for: #: #: ``[0]``: #: the user-interface setting when the file was created. #: #: ``[1]``: #: the regional settings. #: #: Example: ``(1, 61)`` meaning ``(USA, Australia)``. #: #: This information may give a clue to the correct encoding for an #: unknown codepage. For a long list of observed values, refer to the #: OpenOffice.org documentation for the ``COUNTRY`` record. countries = (0, 0) #: What (if anything) is recorded as the name of the last user to #: save the file. user_name = UNICODE_LITERAL('') #: A list of :class:`~xlrd.formatting.Font` class instances, #: each corresponding to a FONT record. #: #: .. versionadded:: 0.6.1 font_list = [] #: A list of :class:`~xlrd.formatting.XF` class instances, #: each corresponding to an ``XF`` record. #: #: .. versionadded:: 0.6.1 xf_list = [] #: A list of :class:`~xlrd.formatting.Format` objects, each corresponding to #: a ``FORMAT`` record, in the order that they appear in the input file. #: It does *not* contain builtin formats. #: #: If you are creating an output file using (for example) :mod:`xlwt`, #: use this list. #: #: The collection to be used for all visual rendering purposes is #: :attr:`format_map`. #: #: .. versionadded:: 0.6.1 format_list = [] ## #: The mapping from :attr:`~xlrd.formatting.XF.format_key` to #: :class:`~xlrd.formatting.Format` object. #: #: .. versionadded:: 0.6.1 format_map = {} #: This provides access via name to the extended format information for #: both built-in styles and user-defined styles. #: #: It maps ``name`` to ``(built_in, xf_index)``, where #: ``name`` is either the name of a user-defined style, #: or the name of one of the built-in styles. Known built-in names are #: Normal, RowLevel_1 to RowLevel_7, #: ColLevel_1 to ColLevel_7, Comma, Currency, Percent, "Comma [0]", #: "Currency [0]", Hyperlink, and "Followed Hyperlink". #: #: ``built_in`` has the following meanings #: #: 1: #: built-in style #: #: 0: #: user-defined #: #: ``xf_index`` is an index into :attr:`Book.xf_list`. #: #: References: OOo docs s6.99 (``STYLE`` record); Excel UI Format/Style #: #: .. versionadded:: 0.6.1 #: #: Extracted only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.7.4 style_name_map = {} #: This provides definitions for colour indexes. Please refer to #: :ref:`palette` for an explanation #: of how colours are represented in Excel. #: #: Colour indexes into the palette map into ``(red, green, blue)`` tuples. #: "Magic" indexes e.g. ``0x7FFF`` map to ``None``. #: #: :attr:`colour_map` is what you need if you want to render cells on screen #: or in a PDF file. If you are writing an output XLS file, use #: :attr:`palette_record`. #: #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.6.1 colour_map = {} #: If the user has changed any of the colours in the standard palette, the #: XLS file will contain a ``PALETTE`` record with 56 (16 for Excel 4.0 and #: earlier) RGB values in it, and this list will be e.g. #: ``[(r0, b0, g0), ..., (r55, b55, g55)]``. #: Otherwise this list will be empty. This is what you need if you are #: writing an output XLS file. If you want to render cells on screen or in a #: PDF file, use :attr:`colour_map`. #: #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.6.1 palette_record = [] #: Time in seconds to extract the XLS image as a contiguous string #: (or mmap equivalent). load_time_stage_1 = -1.0 #: Time in seconds to parse the data from the contiguous string #: (or mmap equivalent). load_time_stage_2 = -1.0 def sheets(self): """ :returns: A list of all sheets in the book. All sheets not already loaded will be loaded. """ for sheetx in xrange(self.nsheets): if not self._sheet_list[sheetx]: self.get_sheet(sheetx) return self._sheet_list[:] def sheet_by_index(self, sheetx): """ :param sheetx: Sheet index in ``range(nsheets)`` :returns: A :class:`~xlrd.sheet.Sheet`. """ return self._sheet_list[sheetx] or self.get_sheet(sheetx) def __iter__(self): """ Makes iteration through sheets of a book a little more straightforward. Don't free resources after use since it can be called like `list(book)` """ for i in range(self.nsheets): yield self.sheet_by_index(i) def sheet_by_name(self, sheet_name): """ :param sheet_name: Name of the sheet required. :returns: A :class:`~xlrd.sheet.Sheet`. """ try: sheetx = self._sheet_names.index(sheet_name) except ValueError: raise XLRDError('No sheet named <%r>' % sheet_name) return self.sheet_by_index(sheetx) def __getitem__(self, item): """ Allow indexing with sheet name or index. :param item: Name or index of sheet enquired upon :return: :class:`~xlrd.sheet.Sheet`. """ if isinstance(item, int): return self.sheet_by_index(item) else: return self.sheet_by_name(item) def sheet_names(self): """ :returns: A list of the names of all the worksheets in the workbook file. This information is available even when no sheets have yet been loaded. """ return self._sheet_names[:] def sheet_loaded(self, sheet_name_or_index): """ :param sheet_name_or_index: Name or index of sheet enquired upon :returns: ``True`` if sheet is loaded, ``False`` otherwise. .. versionadded:: 0.7.1 """ if isinstance(sheet_name_or_index, int): sheetx = sheet_name_or_index else: try: sheetx = self._sheet_names.index(sheet_name_or_index) except ValueError: raise XLRDError('No sheet named <%r>' % sheet_name_or_index) return bool(self._sheet_list[sheetx]) def unload_sheet(self, sheet_name_or_index): """ :param sheet_name_or_index: Name or index of sheet to be unloaded. .. versionadded:: 0.7.1 """ if isinstance(sheet_name_or_index, int): sheetx = sheet_name_or_index else: try: sheetx = self._sheet_names.index(sheet_name_or_index) except ValueError: raise XLRDError('No sheet named <%r>' % sheet_name_or_index) self._sheet_list[sheetx] = None def release_resources(self): """ This method has a dual purpose. You can call it to release memory-consuming objects and (possibly) a memory-mapped file (:class:`mmap.mmap` object) when you have finished loading sheets in ``on_demand`` mode, but still require the :class:`Book` object to examine the loaded sheets. It is also called automatically (a) when :func:`~xlrd.open_workbook` raises an exception and (b) if you are using a ``with`` statement, when the ``with`` block is exited. Calling this method multiple times on the same object has no ill effect. """ self._resources_released = 1 if hasattr(self.mem, "close"): # must be a mmap.mmap object self.mem.close() self.mem = None if hasattr(self.filestr, "close"): self.filestr.close() self.filestr = None self._sharedstrings = None self._rich_text_runlist_map = None def __enter__(self): return self def __exit__(self, exc_type, exc_value, exc_tb): self.release_resources() # return false #: A mapping from ``(lower_case_name, scope)`` to a single :class:`Name` #: object. #: #: .. versionadded:: 0.6.0 name_and_scope_map = {} #: A mapping from `lower_case_name` to a list of :class:`Name` objects. #: The list is sorted in scope order. Typically there will be one item #: (of global scope) in the list. #: #: .. versionadded:: 0.6.0 name_map = {} def __init__(self): self._sheet_list = [] self._sheet_names = [] self._sheet_visibility = [] # from BOUNDSHEET record self.nsheets = 0 self._sh_abs_posn = [] # sheet's absolute position in the stream self._sharedstrings = [] self._rich_text_runlist_map = {} self.raw_user_name = False self._sheethdr_count = 0 # BIFF 4W only self.builtinfmtcount = -1 # unknown as yet. BIFF 3, 4S, 4W self.initialise_format_info() self._all_sheets_count = 0 # includes macro & VBA sheets self._supbook_count = 0 self._supbook_locals_inx = None self._supbook_addins_inx = None self._all_sheets_map = [] # maps an all_sheets index to a calc-sheets index (or -1) self._externsheet_info = [] self._externsheet_type_b57 = [] self._extnsht_name_from_num = {} self._sheet_num_from_name = {} self._extnsht_count = 0 self._supbook_types = [] self._resources_released = 0 self.addin_func_names = [] self.name_obj_list = [] self.colour_map = {} self.palette_record = [] self.xf_list = [] self.style_name_map = {} self.mem = b'' self.filestr = b'' def biff2_8_load(self, filename=None, file_contents=None, logfile=sys.stdout, verbosity=0, use_mmap=True, encoding_override=None, formatting_info=False, on_demand=False, ragged_rows=False, ignore_workbook_corruption=False ): # DEBUG = 0 self.logfile = logfile self.verbosity = verbosity self.use_mmap = use_mmap self.encoding_override = encoding_override self.formatting_info = formatting_info self.on_demand = on_demand self.ragged_rows = ragged_rows if not file_contents: with open(filename, "rb") as f: f.seek(0, 2) # EOF size = f.tell() f.seek(0, 0) # BOF if size == 0: raise XLRDError("File size is 0 bytes") if self.use_mmap: self.filestr = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ) self.stream_len = size else: self.filestr = f.read() self.stream_len = len(self.filestr) else: self.filestr = file_contents self.stream_len = len(file_contents) self.base = 0 if self.filestr[:8] != compdoc.SIGNATURE: # got this one at the antique store self.mem = self.filestr else: cd = compdoc.CompDoc(self.filestr, logfile=self.logfile, ignore_workbook_corruption=ignore_workbook_corruption) for qname in ['Workbook', 'Book']: self.mem, self.base, self.stream_len = \ cd.locate_named_stream(UNICODE_LITERAL(qname)) if self.mem: break else: raise XLRDError("Can't find workbook in OLE2 compound document") del cd if self.mem is not self.filestr: if hasattr(self.filestr, "close"): self.filestr.close() self.filestr = b'' self._position = self.base if DEBUG: print("mem: %s, base: %d, len: %d" % (type(self.mem), self.base, self.stream_len), file=self.logfile) def initialise_format_info(self): # needs to be done once per sheet for BIFF 4W :-( self.format_map = {} self.format_list = [] self.xfcount = 0 self.actualfmtcount = 0 # number of FORMAT records seen so far self._xf_index_to_xl_type_map = {0: XL_CELL_NUMBER} self._xf_epilogue_done = 0 self.xf_list = [] self.font_list = [] def get2bytes(self): pos = self._position buff_two = self.mem[pos:pos+2] lenbuff = len(buff_two) self._position += lenbuff if lenbuff < 2: return MY_EOF lo, hi = buff_two return (BYTES_ORD(hi) << 8) | BYTES_ORD(lo) def get_record_parts(self): pos = self._position mem = self.mem code, length = unpack('= 2: fprintf(self.logfile, "BOUNDSHEET: inx=%d vis=%r sheet_name=%r abs_posn=%d sheet_type=0x%02x\n", self._all_sheets_count, visibility, sheet_name, abs_posn, sheet_type) self._all_sheets_count += 1 if sheet_type != XL_BOUNDSHEET_WORKSHEET: self._all_sheets_map.append(-1) descr = { 1: 'Macro sheet', 2: 'Chart', 6: 'Visual Basic module', }.get(sheet_type, 'UNKNOWN') if DEBUG or self.verbosity >= 1: fprintf(self.logfile, "NOTE *** Ignoring non-worksheet data named %r (type 0x%02x = %s)\n", sheet_name, sheet_type, descr) else: snum = len(self._sheet_names) self._all_sheets_map.append(snum) self._sheet_names.append(sheet_name) self._sh_abs_posn.append(abs_posn) self._sheet_visibility.append(visibility) self._sheet_num_from_name[sheet_name] = snum def handle_builtinfmtcount(self, data): ### N.B. This count appears to be utterly useless. # DEBUG = 1 builtinfmtcount = unpack('= 2: fprintf(self.logfile, "*** No CODEPAGE record; assuming 1200 (utf_16_le)\n") else: codepage = self.codepage if codepage in encoding_from_codepage: encoding = encoding_from_codepage[codepage] elif 300 <= codepage <= 1999: encoding = 'cp' + str(codepage) elif self.biff_version >= 80: self.codepage = 1200 encoding = 'utf_16_le' else: encoding = 'unknown_codepage_' + str(codepage) if DEBUG or (self.verbosity and encoding != self.encoding) : fprintf(self.logfile, "CODEPAGE: codepage %r -> encoding %r\n", codepage, encoding) self.encoding = encoding if self.codepage != 1200: # utf_16_le # If we don't have a codec that can decode ASCII into Unicode, # we're well & truly stuffed -- let the punter know ASAP. try: unicode(b'trial', self.encoding) except BaseException as e: fprintf(self.logfile, "ERROR *** codepage %r -> encoding %r -> %s: %s\n", self.codepage, self.encoding, type(e).__name__.split(".")[-1], e) raise if self.raw_user_name: strg = unpack_string(self.user_name, 0, self.encoding, lenlen=1) strg = strg.rstrip() # if DEBUG: # print "CODEPAGE: user name decoded from %r to %r" % (self.user_name, strg) self.user_name = strg self.raw_user_name = False return self.encoding def handle_codepage(self, data): # DEBUG = 0 codepage = unpack('= 2 if self.biff_version >= 80: option_flags, other_info =unpack("= 1 blah2 = DEBUG or self.verbosity >= 2 if self.biff_version >= 80: num_refs = unpack("= 2: logf = self.logfile fprintf(logf, "FILEPASS:\n") hex_char_dump(data, 0, len(data), base=0, fout=logf) if self.biff_version >= 80: kind1, = unpack('= 2 bv = self.biff_version if bv < 50: return self.derive_encoding() # print # hex_char_dump(data, 0, len(data), fout=self.logfile) ( option_flags, kb_shortcut, name_len, fmla_len, extsht_index, sheet_index, menu_text_len, description_text_len, help_topic_text_len, status_bar_text_len, ) = unpack("> nshift) macro_flag = " M"[nobj.macro] if bv < 80: internal_name, pos = unpack_string_update_pos(data, 14, self.encoding, known_len=name_len) else: internal_name, pos = unpack_unicode_update_pos(data, 14, known_len=name_len) nobj.extn_sheet_num = extsht_index nobj.excel_sheet_index = sheet_index nobj.scope = None # patched up in the names_epilogue() method if blah: fprintf( self.logfile, "NAME[%d]:%s oflags=%d, name_len=%d, fmla_len=%d, extsht_index=%d, sheet_index=%d, name=%r\n", name_index, macro_flag, option_flags, name_len, fmla_len, extsht_index, sheet_index, internal_name) name = internal_name if nobj.builtin: name = builtin_name_from_code.get(name, "??Unknown??") if blah: print(" builtin: %s" % name, file=self.logfile) nobj.name = name nobj.raw_formula = data[pos:] nobj.basic_formula_len = fmla_len nobj.evaluated = 0 if blah: nobj.dump( self.logfile, header="--- handle_name: name[%d] ---" % name_index, footer="-------------------", ) def names_epilogue(self): blah = self.verbosity >= 2 f = self.logfile if blah: print("+++++ names_epilogue +++++", file=f) print("_all_sheets_map", REPR(self._all_sheets_map), file=f) print("_extnsht_name_from_num", REPR(self._extnsht_name_from_num), file=f) print("_sheet_num_from_name", REPR(self._sheet_num_from_name), file=f) num_names = len(self.name_obj_list) for namex in range(num_names): nobj = self.name_obj_list[namex] # Convert from excel_sheet_index to scope. # This is done here because in BIFF7 and earlier, the # BOUNDSHEET records (from which _all_sheets_map is derived) # come after the NAME records. if self.biff_version >= 80: sheet_index = nobj.excel_sheet_index if sheet_index == 0: intl_sheet_index = -1 # global elif 1 <= sheet_index <= len(self._all_sheets_map): intl_sheet_index = self._all_sheets_map[sheet_index-1] if intl_sheet_index == -1: # maps to a macro or VBA sheet intl_sheet_index = -2 # valid sheet reference but not useful else: # huh? intl_sheet_index = -3 # invalid elif 50 <= self.biff_version <= 70: sheet_index = nobj.extn_sheet_num if sheet_index == 0: intl_sheet_index = -1 # global else: sheet_name = self._extnsht_name_from_num[sheet_index] intl_sheet_index = self._sheet_num_from_name.get(sheet_name, -2) nobj.scope = intl_sheet_index for namex in range(num_names): nobj = self.name_obj_list[namex] # Parse the formula ... if nobj.macro or nobj.binary: continue if nobj.evaluated: continue evaluate_name_formula(self, nobj, namex, blah=blah) if self.verbosity >= 2: print("---------- name object dump ----------", file=f) for namex in range(num_names): nobj = self.name_obj_list[namex] nobj.dump(f, header="--- name[%d] ---" % namex) print("--------------------------------------", file=f) # # Build some dicts for access to the name objects # name_and_scope_map = {} # (name.lower(), scope): Name_object name_map = {} # name.lower() : list of Name_objects (sorted in scope order) for namex in range(num_names): nobj = self.name_obj_list[namex] name_lcase = nobj.name.lower() key = (name_lcase, nobj.scope) if key in name_and_scope_map and self.verbosity: fprintf(f, 'Duplicate entry %r in name_and_scope_map\n', key) name_and_scope_map[key] = nobj sort_data = (nobj.scope, namex, nobj) # namex (a temp unique ID) ensures the Name objects will not # be compared (fatal in py3) if name_lcase in name_map: name_map[name_lcase].append(sort_data) else: name_map[name_lcase] = [sort_data] for key in name_map.keys(): alist = name_map[key] alist.sort() name_map[key] = [x[2] for x in alist] self.name_and_scope_map = name_and_scope_map self.name_map = name_map def handle_obj(self, data): # Not doing much handling at all. # Worrying about embedded (BOF ... EOF) substreams is done elsewhere. # DEBUG = 1 obj_type, obj_id = unpack(' handle_obj type=%d id=0x%08x" % (obj_type, obj_id) def handle_supbook(self, data): # aka EXTERNALBOOK in OOo docs self._supbook_types.append(None) blah = DEBUG or self.verbosity >= 2 if blah: print("SUPBOOK:", file=self.logfile) hex_char_dump(data, 0, len(data), fout=self.logfile) num_sheets = unpack("= 2: fprintf(self.logfile, "SST: unique strings: %d\n", uniquestrings) while 1: code, nb, data = self.get_record_parts_conditional(XL_CONTINUE) if code is None: break nbt += nb if DEBUG >= 2: fprintf(self.logfile, "CONTINUE: adding %d bytes to SST -> %d\n", nb, nbt) strlist.append(data) self._sharedstrings, rt_runlist = unpack_SST_table(strlist, uniquestrings) if self.formatting_info: self._rich_text_runlist_map = rt_runlist if DEBUG: t1 = perf_counter() print("SST processing took %.2f seconds" % (t1 - t0, ), file=self.logfile) def handle_writeaccess(self, data): DEBUG = 0 if self.biff_version < 80: if not self.encoding: self.raw_user_name = True self.user_name = data return strg = unpack_string(data, 0, self.encoding, lenlen=1) else: try: strg = unpack_unicode(data, 0, lenlen=2) except UnicodeDecodeError: # may have invalid trailing characters strg = unpack_unicode(data.strip(), 0, lenlen=2) if DEBUG: fprintf(self.logfile, "WRITEACCESS: %d bytes; raw=%s %r\n", len(data), self.raw_user_name, strg) strg = strg.rstrip() self.user_name = strg def parse_globals(self): # DEBUG = 0 # no need to position, just start reading (after the BOF) formatting.initialise_book(self) while 1: rc, length, data = self.get_record_parts() if DEBUG: print("parse_globals: record code is 0x%04x" % rc, file=self.logfile) if rc == XL_SST: self.handle_sst(data) elif rc == XL_FONT or rc == XL_FONT_B3B4: self.handle_font(data) elif rc == XL_FORMAT: # XL_FORMAT2 is BIFF <= 3.0, can't appear in globals self.handle_format(data) elif rc == XL_XF: self.handle_xf(data) elif rc == XL_BOUNDSHEET: self.handle_boundsheet(data) elif rc == XL_DATEMODE: self.handle_datemode(data) elif rc == XL_CODEPAGE: self.handle_codepage(data) elif rc == XL_COUNTRY: self.handle_country(data) elif rc == XL_EXTERNNAME: self.handle_externname(data) elif rc == XL_EXTERNSHEET: self.handle_externsheet(data) elif rc == XL_FILEPASS: self.handle_filepass(data) elif rc == XL_WRITEACCESS: self.handle_writeaccess(data) elif rc == XL_SHEETSOFFSET: self.handle_sheetsoffset(data) elif rc == XL_SHEETHDR: self.handle_sheethdr(data) elif rc == XL_SUPBOOK: self.handle_supbook(data) elif rc == XL_NAME: self.handle_name(data) elif rc == XL_PALETTE: self.handle_palette(data) elif rc == XL_STYLE: self.handle_style(data) elif rc & 0xff == 9 and self.verbosity: fprintf(self.logfile, "*** Unexpected BOF at posn %d: 0x%04x len=%d data=%r\n", self._position - length - 4, rc, length, data) elif rc == XL_EOF: self.xf_epilogue() self.names_epilogue() self.palette_epilogue() if not self.encoding: self.derive_encoding() if self.biff_version == 45: # DEBUG = 0 if DEBUG: print("global EOF: position", self._position, file=self.logfile) # if DEBUG: # pos = self._position - 4 # print repr(self.mem[pos:pos+40]) return else: # if DEBUG: # print >> self.logfile, "parse_globals: ignoring record code 0x%04x" % rc pass def read(self, pos, length): data = self.mem[pos:pos+length] self._position = pos + len(data) return data def getbof(self, rqd_stream): # DEBUG = 1 # if DEBUG: print >> self.logfile, "getbof(): position", self._position if DEBUG: print("reqd: 0x%04x" % rqd_stream, file=self.logfile) def bof_error(msg): raise XLRDError('Unsupported format, or corrupt file: ' + msg) savpos = self._position opcode = self.get2bytes() if opcode == MY_EOF: bof_error('Expected BOF record; met end of file') if opcode not in bofcodes: bof_error('Expected BOF record; found %r' % self.mem[savpos:savpos+8]) length = self.get2bytes() if length == MY_EOF: bof_error('Incomplete BOF record[1]; met end of file') if not (4 <= length <= 20): bof_error( 'Invalid length (%d) for BOF record type 0x%04x' % (length, opcode)) padding = b'\0' * max(0, boflen[opcode] - length) data = self.read(self._position, length) if DEBUG: fprintf(self.logfile, "\ngetbof(): data=%r\n", data) if len(data) < length: bof_error('Incomplete BOF record[2]; met end of file') data += padding version1 = opcode >> 8 version2, streamtype = unpack('= 2: print("BOF: op=0x%04x vers=0x%04x stream=0x%04x buildid=%d buildyr=%d -> BIFF%d" % (opcode, version2, streamtype, build, year, version), file=self.logfile) got_globals = streamtype == XL_WORKBOOK_GLOBALS or ( version == 45 and streamtype == XL_WORKBOOK_GLOBALS_4W) if (rqd_stream == XL_WORKBOOK_GLOBALS and got_globals) or streamtype == rqd_stream: return version if version < 50 and streamtype == XL_WORKSHEET: return version if version >= 50 and streamtype == 0x0100: bof_error("Workspace file -- no spreadsheet data") bof_error( 'BOF not workbook/worksheet: op=0x%04x vers=0x%04x strm=0x%04x build=%d year=%d -> BIFF%d' % (opcode, version2, streamtype, build, year, version) ) # === helper functions def expand_cell_address(inrow, incol): # Ref : OOo docs, "4.3.4 Cell Addresses in BIFF8" outrow = inrow if incol & 0x8000: if outrow >= 32768: outrow -= 65536 relrow = 1 else: relrow = 0 outcol = incol & 0xFF if incol & 0x4000: if outcol >= 128: outcol -= 256 relcol = 1 else: relcol = 0 return outrow, outcol, relrow, relcol def colname(colx, _A2Z="ABCDEFGHIJKLMNOPQRSTUVWXYZ"): assert colx >= 0 name = UNICODE_LITERAL('') while 1: quot, rem = divmod(colx, 26) name = _A2Z[rem] + name if not quot: return name colx = quot - 1 def display_cell_address(rowx, colx, relrow, relcol): if relrow: rowpart = "(*%s%d)" % ("+-"[rowx < 0], abs(rowx)) else: rowpart = "$%d" % (rowx+1,) if relcol: colpart = "(*%s%d)" % ("+-"[colx < 0], abs(colx)) else: colpart = "$" + colname(colx) return colpart + rowpart def unpack_SST_table(datatab, nstrings): "Return list of strings" datainx = 0 ndatas = len(datatab) data = datatab[0] datalen = len(data) pos = 8 strings = [] strappend = strings.append richtext_runs = {} local_unpack = unpack local_min = min local_BYTES_ORD = BYTES_ORD latin_1 = "latin_1" for _unused_i in xrange(nstrings): nchars = local_unpack('> 1, charsneed) rawstrg = data[pos:pos+2*charsavail] # if DEBUG: print "SST U16: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg) try: accstrg += unicode(rawstrg, "utf_16_le") except: # print "SST U16: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg) # Probable cause: dodgy data e.g. unfinished surrogate pair. # E.g. file unicode2.xls in pyExcelerator's examples has cells containing # unichr(i) for i in range(0x100000) # so this will include 0xD800 etc raise pos += 2*charsavail else: # Note: this is COMPRESSED (not ASCII!) encoding!!! charsavail = local_min(datalen - pos, charsneed) rawstrg = data[pos:pos+charsavail] # if DEBUG: print "SST CMPRSD: nchars=%d pos=%d rawstrg=%r" % (nchars, pos, rawstrg) accstrg += unicode(rawstrg, latin_1) pos += charsavail charsgot += charsavail if charsgot == nchars: break datainx += 1 data = datatab[datainx] datalen = len(data) options = local_BYTES_ORD(data[0]) pos = 1 if rtcount: runs = [] for runindex in xrange(rtcount): if pos == datalen: pos = 0 datainx += 1 data = datatab[datainx] datalen = len(data) runs.append(local_unpack("= datalen: # adjust to correct position in next record pos = pos - datalen datainx += 1 if datainx < ndatas: data = datatab[datainx] datalen = len(data) else: assert _unused_i == nstrings - 1 strappend(accstrg) return strings, richtext_runs xlrd-2.0.1/xlrd/compdoc.py000066400000000000000000000511431376464300000154410ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. # No part of the content of this file was derived from the works of # David Giffin. """ Implements the minimal functionality required to extract a "Workbook" or "Book" stream (as one big string) from an OLE2 Compound Document file. """ from __future__ import print_function import array import sys from struct import unpack from .timemachine import * #: Magic cookie that should appear in the first 8 bytes of the file. SIGNATURE = b"\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" EOCSID = -2 FREESID = -1 SATSID = -3 MSATSID = -4 EVILSID = -5 class CompDocError(Exception): pass class DirNode(object): def __init__(self, DID, dent, DEBUG=0, logfile=sys.stdout): # dent is the 128-byte directory entry self.DID = DID self.logfile = logfile (cbufsize, self.etype, self.colour, self.left_DID, self.right_DID, self.root_DID) = \ unpack(' 20: # allows for 2**20 bytes i.e. 1MB print("WARNING: sector size (2**%d) is preposterous; assuming 512 and continuing ..." % ssz, file=logfile) ssz = 9 if sssz > ssz: print("WARNING: short stream sector size (2**%d) is preposterous; assuming 64 and continuing ..." % sssz, file=logfile) sssz = 6 self.sec_size = sec_size = 1 << ssz self.short_sec_size = 1 << sssz if self.sec_size != 512 or self.short_sec_size != 64: print("@@@@ sec_size=%d short_sec_size=%d" % (self.sec_size, self.short_sec_size), file=logfile) ( SAT_tot_secs, self.dir_first_sec_sid, _unused, self.min_size_std_stream, SSAT_first_sec_sid, SSAT_tot_secs, MSATX_first_sec_sid, MSATX_tot_secs, ) = unpack(' 1: print('MSATX: sid=%d (0x%08X)' % (sid, sid), file=logfile) if sid >= mem_data_secs: msg = "MSAT extension: accessing sector %d but only %d in file" % (sid, mem_data_secs) if DEBUG > 1: print(msg, file=logfile) break raise CompDocError(msg) elif sid < 0: raise CompDocError("MSAT extension: invalid sector id: %d" % sid) if seen[sid]: raise CompDocError("MSAT corruption: seen[%d] == %d" % (sid, seen[sid])) seen[sid] = 1 actual_MSATX_sectors += 1 if DEBUG and actual_MSATX_sectors > expected_MSATX_sectors: print("[1]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile) offset = 512 + sec_size * sid MSAT.extend(unpack(fmt, mem[offset:offset+sec_size])) sid = MSAT.pop() # last sector id is sid of next sector in the chain if DEBUG and actual_MSATX_sectors != expected_MSATX_sectors: print("[2]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile) if DEBUG: print("MSAT: len =", len(MSAT), file=logfile) dump_list(MSAT, 10, logfile) # # === build the SAT === # self.SAT = [] actual_SAT_sectors = 0 dump_again = 0 for msidx in xrange(len(MSAT)): msid = MSAT[msidx] if msid in (FREESID, EOCSID): # Specification: the MSAT array may be padded with trailing FREESID entries. # Toleration: a FREESID or EOCSID entry anywhere in the MSAT array will be ignored. continue if msid >= mem_data_secs: if not trunc_warned: print("WARNING *** File is truncated, or OLE2 MSAT is corrupt!!", file=logfile) print("INFO: Trying to access sector %d but only %d available" % (msid, mem_data_secs), file=logfile) trunc_warned = 1 MSAT[msidx] = EVILSID dump_again = 1 continue elif msid < -2: raise CompDocError("MSAT: invalid sector id: %d" % msid) if seen[msid]: raise CompDocError("MSAT extension corruption: seen[%d] == %d" % (msid, seen[msid])) seen[msid] = 2 actual_SAT_sectors += 1 if DEBUG and actual_SAT_sectors > SAT_sectors_reqd: print("[3]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, actual_SAT_sectors, msid, file=logfile) offset = 512 + sec_size * msid self.SAT.extend(unpack(fmt, mem[offset:offset+sec_size])) if DEBUG: print("SAT: len =", len(self.SAT), file=logfile) dump_list(self.SAT, 10, logfile) # print >> logfile, "SAT ", # for i, s in enumerate(self.SAT): # print >> logfile, "entry: %4d offset: %6d, next entry: %4d" % (i, 512 + sec_size * i, s) # print >> logfile, "%d:%d " % (i, s), print(file=logfile) if DEBUG and dump_again: print("MSAT: len =", len(MSAT), file=logfile) dump_list(MSAT, 10, logfile) for satx in xrange(mem_data_secs, len(self.SAT)): self.SAT[satx] = EVILSID print("SAT: len =", len(self.SAT), file=logfile) dump_list(self.SAT, 10, logfile) # # === build the directory === # dbytes = self._get_stream( self.mem, 512, self.SAT, self.sec_size, self.dir_first_sec_sid, name="directory", seen_id=3) dirlist = [] did = -1 for pos in xrange(0, len(dbytes), 128): did += 1 dirlist.append(DirNode(did, dbytes[pos:pos+128], 0, logfile)) self.dirlist = dirlist _build_family_tree(dirlist, 0, dirlist[0].root_DID) # and stand well back ... if DEBUG: for d in dirlist: d.dump(DEBUG) # # === get the SSCS === # sscs_dir = self.dirlist[0] assert sscs_dir.etype == 5 # root entry if sscs_dir.first_SID < 0 or sscs_dir.tot_size == 0: # Problem reported by Frank Hoffsuemmer: some software was # writing -1 instead of -2 (EOCSID) for the first_SID # when the SCCS was empty. Not having EOCSID caused assertion # failure in _get_stream. # Solution: avoid calling _get_stream in any case when the # SCSS appears to be empty. self.SSCS = "" else: self.SSCS = self._get_stream( self.mem, 512, self.SAT, sec_size, sscs_dir.first_SID, sscs_dir.tot_size, name="SSCS", seen_id=4) # if DEBUG: print >> logfile, "SSCS", repr(self.SSCS) # # === build the SSAT === # self.SSAT = [] if SSAT_tot_secs > 0 and sscs_dir.tot_size == 0: print("WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero", file=logfile) if sscs_dir.tot_size > 0: sid = SSAT_first_sec_sid nsecs = SSAT_tot_secs while sid >= 0 and nsecs > 0: if seen[sid]: raise CompDocError("SSAT corruption: seen[%d] == %d" % (sid, seen[sid])) seen[sid] = 5 nsecs -= 1 start_pos = 512 + sid * sec_size news = list(unpack(fmt, mem[start_pos:start_pos+sec_size])) self.SSAT.extend(news) sid = self.SAT[sid] if DEBUG: print("SSAT last sid %d; remaining sectors %d" % (sid, nsecs), file=logfile) assert nsecs == 0 and sid == EOCSID if DEBUG: print("SSAT", file=logfile) dump_list(self.SSAT, 10, logfile) if DEBUG: print("seen", file=logfile) dump_list(seen, 20, logfile) def _get_stream(self, mem, base, sat, sec_size, start_sid, size=None, name='', seen_id=None): # print >> self.logfile, "_get_stream", base, sec_size, start_sid, size sectors = [] s = start_sid if size is None: # nothing to check against while s >= 0: if seen_id is not None: if self.seen[s]: raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s])) self.seen[s] = seen_id start_pos = base + s * sec_size sectors.append(mem[start_pos:start_pos+sec_size]) try: s = sat[s] except IndexError: raise CompDocError( "OLE2 stream %r: sector allocation table invalid entry (%d)" % (name, s) ) assert s == EOCSID else: todo = size while s >= 0: if seen_id is not None: if self.seen[s]: raise CompDocError("%s corruption: seen[%d] == %d" % (name, s, self.seen[s])) self.seen[s] = seen_id start_pos = base + s * sec_size grab = sec_size if grab > todo: grab = todo todo -= grab sectors.append(mem[start_pos:start_pos+grab]) try: s = sat[s] except IndexError: raise CompDocError( "OLE2 stream %r: sector allocation table invalid entry (%d)" % (name, s) ) assert s == EOCSID if todo != 0: fprintf(self.logfile, "WARNING *** OLE2 stream %r: expected size %d, actual size %d\n", name, size, size - todo) return b''.join(sectors) def _dir_search(self, path, storage_DID=0): # Return matching DirNode instance, or None head = path[0] tail = path[1:] dl = self.dirlist for child in dl[storage_DID].children: if dl[child].name.lower() == head.lower(): et = dl[child].etype if et == 2: return dl[child] if et == 1: if not tail: raise CompDocError("Requested component is a 'storage'") return self._dir_search(tail, child) dl[child].dump(1) raise CompDocError("Requested stream is not a 'user stream'") return None def get_named_stream(self, qname): """ Interrogate the compound document's directory; return the stream as a string if found, otherwise return ``None``. :param qname: Name of the desired stream e.g. ``'Workbook'``. Should be in Unicode or convertible thereto. """ d = self._dir_search(qname.split("/")) if d is None: return None if d.tot_size >= self.min_size_std_stream: return self._get_stream( self.mem, 512, self.SAT, self.sec_size, d.first_SID, d.tot_size, name=qname, seen_id=d.DID+6) else: return self._get_stream( self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID, d.tot_size, name=qname + " (from SSCS)", seen_id=None) def locate_named_stream(self, qname): """ Interrogate the compound document's directory. If the named stream is not found, ``(None, 0, 0)`` will be returned. If the named stream is found and is contiguous within the original byte sequence (``mem``) used when the document was opened, then ``(mem, offset_to_start_of_stream, length_of_stream)`` is returned. Otherwise a new string is built from the fragments and ``(new_string, 0, length_of_stream)`` is returned. :param qname: Name of the desired stream e.g. ``'Workbook'``. Should be in Unicode or convertible thereto. """ d = self._dir_search(qname.split("/")) if d is None: return (None, 0, 0) if d.tot_size > self.mem_data_len: raise CompDocError("%r stream length (%d bytes) > file data size (%d bytes)" % (qname, d.tot_size, self.mem_data_len)) if d.tot_size >= self.min_size_std_stream: result = self._locate_stream( self.mem, 512, self.SAT, self.sec_size, d.first_SID, d.tot_size, qname, d.DID+6) if self.DEBUG: print("\nseen", file=self.logfile) dump_list(self.seen, 20, self.logfile) return result else: return ( self._get_stream( self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID, d.tot_size, qname + " (from SSCS)", None), 0, d.tot_size, ) def _locate_stream(self, mem, base, sat, sec_size, start_sid, expected_stream_size, qname, seen_id): # print >> self.logfile, "_locate_stream", base, sec_size, start_sid, expected_stream_size s = start_sid if s < 0: raise CompDocError("_locate_stream: start_sid (%d) is -ve" % start_sid) p = -99 # dummy previous SID start_pos = -9999 end_pos = -8888 slices = [] tot_found = 0 found_limit = (expected_stream_size + sec_size - 1) // sec_size while s >= 0: if self.seen[s]: if not self.ignore_workbook_corruption: print("_locate_stream(%s): seen" % qname, file=self.logfile); dump_list(self.seen, 20, self.logfile) raise CompDocError("%s corruption: seen[%d] == %d" % (qname, s, self.seen[s])) self.seen[s] = seen_id tot_found += 1 if tot_found > found_limit: # Note: expected size rounded up to higher sector raise CompDocError( "%s: size exceeds expected %d bytes; corrupt?" % (qname, found_limit * sec_size) ) if s == p+1: # contiguous sectors end_pos += sec_size else: # start new slice if p >= 0: # not first time slices.append((start_pos, end_pos)) start_pos = base + s * sec_size end_pos = start_pos + sec_size p = s s = sat[s] assert s == EOCSID assert tot_found == found_limit # print >> self.logfile, "_locate_stream(%s): seen" % qname; dump_list(self.seen, 20, self.logfile) if not slices: # The stream is contiguous ... just what we like! return (mem, start_pos, expected_stream_size) slices.append((start_pos, end_pos)) # print >> self.logfile, "+++>>> %d fragments" % len(slices) return (b''.join(mem[start_pos:end_pos] for start_pos, end_pos in slices), 0, expected_stream_size) # ========================================================================================== def x_dump_line(alist, stride, f, dpos, equal=0): print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f) for value in alist[dpos:dpos + stride]: print(str(value), end=' ', file=f) print(file=f) def dump_list(alist, stride, f=sys.stdout): def _dump_line(dpos, equal=0): print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f) for value in alist[dpos:dpos + stride]: print(str(value), end=' ', file=f) print(file=f) pos = None oldpos = None for pos in xrange(0, len(alist), stride): if oldpos is None: _dump_line(pos) oldpos = pos elif alist[pos:pos+stride] != alist[oldpos:oldpos+stride]: if pos - oldpos > stride: _dump_line(pos - stride, equal=1) _dump_line(pos) oldpos = pos if oldpos is not None and pos is not None and pos != oldpos: _dump_line(pos, equal=1) xlrd-2.0.1/xlrd/formatting.py000066400000000000000000001310051376464300000161630ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. # No part of the content of this file was derived from the works of # David Giffin. """ Module for formatting information. """ from __future__ import print_function import re from struct import unpack from .biffh import ( FDT, FGE, FNU, FTX, FUN, XL_CELL_DATE, XL_CELL_NUMBER, XL_CELL_TEXT, XL_FORMAT, XL_FORMAT2, BaseObject, XLRDError, fprintf, unpack_string, unpack_unicode, upkbits, upkbitsL, ) from .timemachine import * DEBUG = 0 _cellty_from_fmtty = { FNU: XL_CELL_NUMBER, FUN: XL_CELL_NUMBER, FGE: XL_CELL_NUMBER, FDT: XL_CELL_DATE, FTX: XL_CELL_NUMBER, # Yes, a number can be formatted as text. } excel_default_palette_b5 = ( ( 0, 0, 0), (255, 255, 255), (255, 0, 0), ( 0, 255, 0), ( 0, 0, 255), (255, 255, 0), (255, 0, 255), ( 0, 255, 255), (128, 0, 0), ( 0, 128, 0), ( 0, 0, 128), (128, 128, 0), (128, 0, 128), ( 0, 128, 128), (192, 192, 192), (128, 128, 128), (153, 153, 255), (153, 51, 102), (255, 255, 204), (204, 255, 255), (102, 0, 102), (255, 128, 128), ( 0, 102, 204), (204, 204, 255), ( 0, 0, 128), (255, 0, 255), (255, 255, 0), ( 0, 255, 255), (128, 0, 128), (128, 0, 0), ( 0, 128, 128), ( 0, 0, 255), ( 0, 204, 255), (204, 255, 255), (204, 255, 204), (255, 255, 153), (153, 204, 255), (255, 153, 204), (204, 153, 255), (227, 227, 227), ( 51, 102, 255), ( 51, 204, 204), (153, 204, 0), (255, 204, 0), (255, 153, 0), (255, 102, 0), (102, 102, 153), (150, 150, 150), ( 0, 51, 102), ( 51, 153, 102), ( 0, 51, 0), ( 51, 51, 0), (153, 51, 0), (153, 51, 102), ( 51, 51, 153), ( 51, 51, 51), ) excel_default_palette_b2 = excel_default_palette_b5[:16] # Following table borrowed from Gnumeric 1.4 source. # Checked against OOo docs and MS docs. excel_default_palette_b8 = ( # (red, green, blue) ( 0, 0, 0), (255,255,255), (255, 0, 0), ( 0,255, 0), # 0 ( 0, 0,255), (255,255, 0), (255, 0,255), ( 0,255,255), # 4 (128, 0, 0), ( 0,128, 0), ( 0, 0,128), (128,128, 0), # 8 (128, 0,128), ( 0,128,128), (192,192,192), (128,128,128), # 12 (153,153,255), (153, 51,102), (255,255,204), (204,255,255), # 16 (102, 0,102), (255,128,128), ( 0,102,204), (204,204,255), # 20 ( 0, 0,128), (255, 0,255), (255,255, 0), ( 0,255,255), # 24 (128, 0,128), (128, 0, 0), ( 0,128,128), ( 0, 0,255), # 28 ( 0,204,255), (204,255,255), (204,255,204), (255,255,153), # 32 (153,204,255), (255,153,204), (204,153,255), (255,204,153), # 36 ( 51,102,255), ( 51,204,204), (153,204, 0), (255,204, 0), # 40 (255,153, 0), (255,102, 0), (102,102,153), (150,150,150), # 44 ( 0, 51,102), ( 51,153,102), ( 0, 51, 0), ( 51, 51, 0), # 48 (153, 51, 0), (153, 51,102), ( 51, 51,153), ( 51, 51, 51), # 52 ) default_palette = { 80: excel_default_palette_b8, 70: excel_default_palette_b5, 50: excel_default_palette_b5, 45: excel_default_palette_b2, 40: excel_default_palette_b2, 30: excel_default_palette_b2, 21: excel_default_palette_b2, 20: excel_default_palette_b2, } # 00H = Normal # 01H = RowLevel_lv (see next field) # 02H = ColLevel_lv (see next field) # 03H = Comma # 04H = Currency # 05H = Percent # 06H = Comma [0] (BIFF4-BIFF8) # 07H = Currency [0] (BIFF4-BIFF8) # 08H = Hyperlink (BIFF8) # 09H = Followed Hyperlink (BIFF8) built_in_style_names = [ "Normal", "RowLevel_", "ColLevel_", "Comma", "Currency", "Percent", "Comma [0]", "Currency [0]", "Hyperlink", "Followed Hyperlink", ] def initialise_colour_map(book): book.colour_map = {} book.colour_indexes_used = {} if not book.formatting_info: return # Add the 8 invariant colours for i in xrange(8): book.colour_map[i] = excel_default_palette_b8[i] # Add the default palette depending on the version dpal = default_palette[book.biff_version] ndpal = len(dpal) for i in xrange(ndpal): book.colour_map[i+8] = dpal[i] # Add the specials -- None means the RGB value is not known # System window text colour for border lines book.colour_map[ndpal+8] = None # System window background colour for pattern background book.colour_map[ndpal+8+1] = None # System ToolTip text colour (used in note objects) book.colour_map[0x51] = None # 32767, system window text colour for fonts book.colour_map[0x7FFF] = None def nearest_colour_index(colour_map, rgb, debug=0): """ General purpose function. Uses Euclidean distance. So far used only for pre-BIFF8 ``WINDOW2`` record. Doesn't have to be fast. Doesn't have to be fancy. """ best_metric = 3 * 256 * 256 best_colourx = 0 for colourx, cand_rgb in colour_map.items(): if cand_rgb is None: continue metric = 0 for v1, v2 in zip(rgb, cand_rgb): metric += (v1 - v2) * (v1 - v2) if metric < best_metric: best_metric = metric best_colourx = colourx if metric == 0: break if 0 and debug: print("nearest_colour_index for %r is %r -> %r; best_metric is %d" % (rgb, best_colourx, colour_map[best_colourx], best_metric)) return best_colourx class EqNeAttrs(object): """ This mixin class exists solely so that :class:`Format`, :class:`Font`, and :class:`XF` objects can be compared by value of their attributes. """ def __eq__(self, other): return self.__dict__ == other.__dict__ def __ne__(self, other): return self.__dict__ != other.__dict__ class Font(BaseObject, EqNeAttrs): """ An Excel "font" contains the details of not only what is normally considered a font, but also several other display attributes. Items correspond to those in the Excel UI's Format -> Cells -> Font tab. .. versionadded:: 0.6.1 """ #: 1 = Characters are bold. Redundant; see "weight" attribute. bold = 0 #: Values: #: :: #: #: 0 = ANSI Latin #: 1 = System default #: 2 = Symbol, #: 77 = Apple Roman, #: 128 = ANSI Japanese Shift-JIS, #: 129 = ANSI Korean (Hangul), #: 130 = ANSI Korean (Johab), #: 134 = ANSI Chinese Simplified GBK, #: 136 = ANSI Chinese Traditional BIG5, #: 161 = ANSI Greek, #: 162 = ANSI Turkish, #: 163 = ANSI Vietnamese, #: 177 = ANSI Hebrew, #: 178 = ANSI Arabic, #: 186 = ANSI Baltic, #: 204 = ANSI Cyrillic, #: 222 = ANSI Thai, #: 238 = ANSI Latin II (Central European), #: 255 = OEM Latin I character_set = 0 #: An explanation of "colour index" is given in :ref:`palette`. colour_index = 0 #: 1 = Superscript, 2 = Subscript. escapement = 0 #: Values: #: :: #: #: 0 = None (unknown or don't care) #: 1 = Roman (variable width, serifed) #: 2 = Swiss (variable width, sans-serifed) #: 3 = Modern (fixed width, serifed or sans-serifed) #: 4 = Script (cursive) #: 5 = Decorative (specialised, for example Old English, Fraktur) family = 0 #: The 0-based index used to refer to this Font() instance. #: Note that index 4 is never used; xlrd supplies a dummy place-holder. font_index = 0 #: Height of the font (in twips). A twip = 1/20 of a point. height = 0 #: 1 = Characters are italic. italic = 0 #: The name of the font. Example: ``"Arial"``. name = UNICODE_LITERAL("") #: 1 = Characters are struck out. struck_out = 0 #: Values: #: :: #: #: 0 = None #: 1 = Single; 0x21 (33) = Single accounting #: 2 = Double; 0x22 (34) = Double accounting underline_type = 0 #: 1 = Characters are underlined. Redundant; see #: :attr:`underline_type` attribute. underlined = 0 #: Font weight (100-1000). Standard values are 400 for normal text #: and 700 for bold text. weight = 400 #: 1 = Font is outline style (Macintosh only) outline = 0 #: 1 = Font is shadow style (Macintosh only) shadow = 0 def handle_efont(book, data): # BIFF2 only if not book.formatting_info: return book.font_list[-1].colour_index = unpack('= 2 bv = book.biff_version k = len(book.font_list) if k == 4: f = Font() f.name = UNICODE_LITERAL('Dummy Font') f.font_index = k book.font_list.append(f) k += 1 f = Font() f.font_index = k book.font_list.append(f) if bv >= 50: ( f.height, option_flags, f.colour_index, f.weight, f.escapement, f.underline_type, f.family, f.character_set, ) = unpack('> 1 f.underlined = (option_flags & 4) >> 2 f.struck_out = (option_flags & 8) >> 3 f.outline = (option_flags & 16) >> 4 f.shadow = (option_flags & 32) >> 5 if bv >= 80: f.name = unpack_unicode(data, 14, lenlen=1) else: f.name = unpack_string(data, 14, book.encoding, lenlen=1) elif bv >= 30: f.height, option_flags, f.colour_index = unpack('> 1 f.underlined = (option_flags & 4) >> 2 f.struck_out = (option_flags & 8) >> 3 f.outline = (option_flags & 16) >> 4 f.shadow = (option_flags & 32) >> 5 f.name = unpack_string(data, 6, book.encoding, lenlen=1) # Now cook up the remaining attributes ... f.weight = [400, 700][f.bold] f.escapement = 0 # None f.underline_type = f.underlined # None or Single f.family = 0 # Unknown / don't care f.character_set = 1 # System default (0 means "ANSI Latin") else: # BIFF2 f.height, option_flags = unpack('> 1 f.underlined = (option_flags & 4) >> 2 f.struck_out = (option_flags & 8) >> 3 f.outline = 0 f.shadow = 0 f.name = unpack_string(data, 4, book.encoding, lenlen=1) # Now cook up the remaining attributes ... f.weight = [400, 700][f.bold] f.escapement = 0 # None f.underline_type = f.underlined # None or Single f.family = 0 # Unknown / don't care f.character_set = 1 # System default (0 means "ANSI Latin") if blah: f.dump( book.logfile, header="--- handle_font: font[%d] ---" % f.font_index, footer="-------------------", ) # === "Number formats" === class Format(BaseObject, EqNeAttrs): """ "Number format" information from a ``FORMAT`` record. .. versionadded:: 0.6.1 """ #: The key into :attr:`~xlrd.book.Book.format_map` format_key = 0 #: A classification that has been inferred from the format string. #: Currently, this is used only to distinguish between numbers and dates. #: Values:: #: #: FUN = 0 # unknown #: FDT = 1 # date #: FNU = 2 # number #: FGE = 3 # general #: FTX = 4 # text type = FUN #: The format string format_str = UNICODE_LITERAL('') def __init__(self, format_key, ty, format_str): self.format_key = format_key self.type = ty self.format_str = format_str std_format_strings = { # "std" == "standard for US English locale" # #### TODO ... a lot of work to tailor these to the user's locale. # See e.g. gnumeric-1.x.y/src/formats.c 0x00: "General", 0x01: "0", 0x02: "0.00", 0x03: "#,##0", 0x04: "#,##0.00", 0x05: "$#,##0_);($#,##0)", 0x06: "$#,##0_);[Red]($#,##0)", 0x07: "$#,##0.00_);($#,##0.00)", 0x08: "$#,##0.00_);[Red]($#,##0.00)", 0x09: "0%", 0x0a: "0.00%", 0x0b: "0.00E+00", 0x0c: "# ?/?", 0x0d: "# ??/??", 0x0e: "m/d/yy", 0x0f: "d-mmm-yy", 0x10: "d-mmm", 0x11: "mmm-yy", 0x12: "h:mm AM/PM", 0x13: "h:mm:ss AM/PM", 0x14: "h:mm", 0x15: "h:mm:ss", 0x16: "m/d/yy h:mm", 0x25: "#,##0_);(#,##0)", 0x26: "#,##0_);[Red](#,##0)", 0x27: "#,##0.00_);(#,##0.00)", 0x28: "#,##0.00_);[Red](#,##0.00)", 0x29: "_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", 0x2a: "_($* #,##0_);_($* (#,##0);_($* \"-\"_);_(@_)", 0x2b: "_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", 0x2c: "_($* #,##0.00_);_($* (#,##0.00);_($* \"-\"??_);_(@_)", 0x2d: "mm:ss", 0x2e: "[h]:mm:ss", 0x2f: "mm:ss.0", 0x30: "##0.0E+0", 0x31: "@", } fmt_code_ranges = [ # both-inclusive ranges of "standard" format codes # Source: the openoffice.org doc't # and the OOXML spec Part 4, section 3.8.30 ( 0, 0, FGE), ( 1, 13, FNU), (14, 22, FDT), (27, 36, FDT), # CJK date formats (37, 44, FNU), (45, 47, FDT), (48, 48, FNU), (49, 49, FTX), # Gnumeric assumes (or assumed) that built-in formats finish at 49, not at 163 (50, 58, FDT), # CJK date formats (59, 62, FNU), # Thai number (currency?) formats (67, 70, FNU), # Thai number (currency?) formats (71, 81, FDT), # Thai date formats ] std_format_code_types = {} for lo, hi, ty in fmt_code_ranges: for x in xrange(lo, hi+1): std_format_code_types[x] = ty del lo, hi, ty, x date_chars = UNICODE_LITERAL('ymdhs') # year, month/minute, day, hour, second date_char_dict = {} for _c in date_chars + date_chars.upper(): date_char_dict[_c] = 5 del _c, date_chars skip_char_dict = {} for _c in UNICODE_LITERAL('$-+/(): '): skip_char_dict[_c] = 1 num_char_dict = { UNICODE_LITERAL('0'): 5, UNICODE_LITERAL('#'): 5, UNICODE_LITERAL('?'): 5, } non_date_formats = { UNICODE_LITERAL('0.00E+00'):1, UNICODE_LITERAL('##0.0E+0'):1, UNICODE_LITERAL('General') :1, UNICODE_LITERAL('GENERAL') :1, # OOo Calc 1.1.4 does this. UNICODE_LITERAL('general') :1, # pyExcelerator 0.6.3 does this. UNICODE_LITERAL('@') :1, } fmt_bracketed_sub = re.compile(r'\[[^]]*\]').sub # Boolean format strings (actual cases) # '"Yes";"Yes";"No"' # '"True";"True";"False"' # '"On";"On";"Off"' def is_date_format_string(book, fmt): # Heuristics: # Ignore "text" and [stuff in square brackets (aarrgghh -- see below)]. # Handle backslashed-escaped chars properly. # E.g. hh\hmm\mss\s should produce a display like 23h59m59s # Date formats have one or more of ymdhs (caseless) in them. # Numeric formats have # and 0. # N.B. 'General"."' hence get rid of "text" first. # TODO: Find where formats are interpreted in Gnumeric # TODO: '[h]\\ \\h\\o\\u\\r\\s' ([h] means don't care about hours > 23) state = 0 s = '' for c in fmt: if state == 0: if c == UNICODE_LITERAL('"'): state = 1 elif c in UNICODE_LITERAL(r"\_*"): state = 2 elif c in skip_char_dict: pass else: s += c elif state == 1: if c == UNICODE_LITERAL('"'): state = 0 elif state == 2: # Ignore char after backslash, underscore or asterisk state = 0 assert 0 <= state <= 2 if book.verbosity >= 4: print("is_date_format_string: reduced format is %s" % REPR(s), file=book.logfile) s = fmt_bracketed_sub('', s) if s in non_date_formats: return False state = 0 separator = ";" got_sep = 0 date_count = num_count = 0 for c in s: if c in date_char_dict: date_count += date_char_dict[c] elif c in num_char_dict: num_count += num_char_dict[c] elif c == separator: got_sep = 1 # print num_count, date_count, repr(fmt) if date_count and not num_count: return True if num_count and not date_count: return False if date_count: if book.verbosity: fprintf(book.logfile, 'WARNING *** is_date_format: ambiguous d=%d n=%d fmt=%r\n', date_count, num_count, fmt) elif not got_sep: if book.verbosity: fprintf(book.logfile, "WARNING *** format %r produces constant result\n", fmt) return date_count > num_count def handle_format(self, data, rectype=XL_FORMAT): DEBUG = 0 bv = self.biff_version if rectype == XL_FORMAT2: bv = min(bv, 30) if not self.encoding: self.derive_encoding() strpos = 2 if bv >= 50: fmtkey = unpack('= 80: unistrg = unpack_unicode(data, 2) else: unistrg = unpack_string(data, strpos, self.encoding, lenlen=1) blah = DEBUG or self.verbosity >= 3 if blah: fprintf(self.logfile, "FORMAT: count=%d fmtkey=0x%04x (%d) s=%r\n", self.actualfmtcount, fmtkey, fmtkey, unistrg) is_date_s = self.is_date_format_string(unistrg) ty = [FGE, FDT][is_date_s] if not(fmtkey > 163 or bv < 50): # user_defined if fmtkey > 163 # N.B. Gnumeric incorrectly starts these at 50 instead of 164 :-( # if earlier than BIFF 5, standard info is useless std_ty = std_format_code_types.get(fmtkey, FUN) # print "std ty", std_ty is_date_c = std_ty == FDT if self.verbosity and 0 < fmtkey < 50 and (is_date_c ^ is_date_s): DEBUG = 2 fprintf(self.logfile, "WARNING *** Conflict between " "std format key %d and its format string %r\n", fmtkey, unistrg) if DEBUG == 2: fprintf(self.logfile, "ty: %d; is_date_c: %r; is_date_s: %r; fmt_strg: %r", ty, is_date_c, is_date_s, unistrg) fmtobj = Format(fmtkey, ty, unistrg) if blah: fmtobj.dump(self.logfile, header="--- handle_format [%d] ---" % (self.actualfmtcount-1, )) self.format_map[fmtkey] = fmtobj self.format_list.append(fmtobj) # ============================================================================= def handle_palette(book, data): if not book.formatting_info: return blah = DEBUG or book.verbosity >= 2 n_colours, = unpack('= 50] if (DEBUG or book.verbosity >= 1) and n_colours != expected_n_colours: fprintf(book.logfile, "NOTE *** Expected %d colours in PALETTE record, found %d\n", expected_n_colours, n_colours) elif blah: fprintf(book.logfile, "PALETTE record with %d colours\n", n_colours) fmt = '> 8) & 0xff blue = (c >> 16) & 0xff old_rgb = book.colour_map[8+i] new_rgb = (red, green, blue) book.palette_record.append(new_rgb) book.colour_map[8+i] = new_rgb if blah: if new_rgb != old_rgb: print("%2d: %r -> %r" % (i, old_rgb, new_rgb), file=book.logfile) def palette_epilogue(book): # Check colour indexes in fonts etc. # This must be done here as FONT records # come *before* the PALETTE record :-( for font in book.font_list: if font.font_index == 4: # the missing font record continue cx = font.colour_index if cx == 0x7fff: # system window text colour continue if cx in book.colour_map: book.colour_indexes_used[cx] = 1 elif book.verbosity: print("Size of colour table:", len(book.colour_map), file=book.logfile) fprintf(book.logfile, "*** Font #%d (%r): colour index 0x%04x is unknown\n", font.font_index, font.name, cx) if book.verbosity >= 1: used = sorted(book.colour_indexes_used.keys()) print("\nColour indexes used:\n%r\n" % used, file=book.logfile) def handle_style(book, data): if not book.formatting_info: return blah = DEBUG or book.verbosity >= 2 bv = book.biff_version flag_and_xfx, built_in_id, level = unpack('= 80: try: name = unpack_unicode(data, 2, lenlen=2) except UnicodeDecodeError: print("STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d" % (built_in, xf_index, built_in_id, level), file=book.logfile) print("raw bytes:", repr(data[2:]), file=book.logfile) raise else: name = unpack_string(data, 2, book.encoding, lenlen=1) if blah and not name: print("WARNING *** A user-defined style has a zero-length name", file=book.logfile) book.style_name_map[name] = (built_in, xf_index) if blah: fprintf(book.logfile, "STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d name=%r\n", built_in, xf_index, built_in_id, level, name) def check_colour_indexes_in_obj(book, obj, orig_index): alist = sorted(obj.__dict__.items()) for attr, nobj in alist: if hasattr(nobj, 'dump'): check_colour_indexes_in_obj(book, nobj, orig_index) elif attr.find('colour_index') >= 0: if nobj in book.colour_map: book.colour_indexes_used[nobj] = 1 continue oname = obj.__class__.__name__ print("*** xf #%d : %s.%s = 0x%04x (unknown)" % (orig_index, oname, attr, nobj), file=book.logfile) def fill_in_standard_formats(book): for x in std_format_code_types.keys(): if x not in book.format_map: ty = std_format_code_types[x] # Note: many standard format codes (mostly CJK date formats) have # format strings that vary by locale; xlrd does not (yet) # handle those; the type (date or numeric) is recorded but the fmt_str will be None. fmt_str = std_format_strings.get(x) fmtobj = Format(x, ty, fmt_str) book.format_map[x] = fmtobj def handle_xf(self, data): # self is a Book instance # DEBUG = 0 blah = DEBUG or self.verbosity >= 3 bv = self.biff_version xf = XF() xf.alignment = XFAlignment() xf.alignment.indent_level = 0 xf.alignment.shrink_to_fit = 0 xf.alignment.text_direction = 0 xf.border = XFBorder() xf.border.diag_up = 0 xf.border.diag_down = 0 xf.border.diag_colour_index = 0 xf.border.diag_line_style = 0 # no line xf.background = XFBackground() xf.protection = XFProtection() # fill in the known standard formats if bv >= 50 and not self.xfcount: # i.e. do this once before we process the first XF record fill_in_standard_formats(self) if bv >= 80: unpack_fmt = '> 2 attr_stems = [ 'format', 'font', 'alignment', 'border', 'background', 'protection', ] for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, reg & 1) reg >>= 1 upkbitsL(xf.border, pkd_brdbkg1, ( (0, 0x0000000f, 'left_line_style'), (4, 0x000000f0, 'right_line_style'), (8, 0x00000f00, 'top_line_style'), (12, 0x0000f000, 'bottom_line_style'), (16, 0x007f0000, 'left_colour_index'), (23, 0x3f800000, 'right_colour_index'), (30, 0x40000000, 'diag_down'), (31, 0x80000000, 'diag_up'), )) upkbits(xf.border, pkd_brdbkg2, ( (0, 0x0000007F, 'top_colour_index'), (7, 0x00003F80, 'bottom_colour_index'), (14, 0x001FC000, 'diag_colour_index'), (21, 0x01E00000, 'diag_line_style'), )) upkbitsL(xf.background, pkd_brdbkg2, ( (26, 0xFC000000, 'fill_pattern'), )) upkbits(xf.background, pkd_brdbkg3, ( (0, 0x007F, 'pattern_colour_index'), (7, 0x3F80, 'background_colour_index'), )) elif bv >= 50: unpack_fmt = '> 2 attr_stems = [ 'format', 'font', 'alignment', 'border', 'background', 'protection', ] for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, reg & 1) reg >>= 1 upkbitsL(xf.background, pkd_brdbkg1, ( ( 0, 0x0000007F, 'pattern_colour_index'), ( 7, 0x00003F80, 'background_colour_index'), (16, 0x003F0000, 'fill_pattern'), )) upkbitsL(xf.border, pkd_brdbkg1, ( (22, 0x01C00000, 'bottom_line_style'), (25, 0xFE000000, 'bottom_colour_index'), )) upkbits(xf.border, pkd_brdbkg2, ( ( 0, 0x00000007, 'top_line_style'), ( 3, 0x00000038, 'left_line_style'), ( 6, 0x000001C0, 'right_line_style'), ( 9, 0x0000FE00, 'top_colour_index'), (16, 0x007F0000, 'left_colour_index'), (23, 0x3F800000, 'right_colour_index'), )) elif bv >= 40: unpack_fmt = '> 6 xf.alignment.rotation = [0, 255, 90, 180][orientation] reg = pkd_used >> 2 attr_stems = [ 'format', 'font', 'alignment', 'border', 'background', 'protection', ] for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, reg & 1) reg >>= 1 upkbits(xf.background, pkd_bkg_34, ( ( 0, 0x003F, 'fill_pattern'), ( 6, 0x07C0, 'pattern_colour_index'), (11, 0xF800, 'background_colour_index'), )) upkbitsL(xf.border, pkd_brd_34, ( ( 0, 0x00000007, 'top_line_style'), ( 3, 0x000000F8, 'top_colour_index'), ( 8, 0x00000700, 'left_line_style'), (11, 0x0000F800, 'left_colour_index'), (16, 0x00070000, 'bottom_line_style'), (19, 0x00F80000, 'bottom_colour_index'), (24, 0x07000000, 'right_line_style'), (27, 0xF8000000, 'right_colour_index'), )) elif bv == 30: unpack_fmt = '> 2 attr_stems = [ 'format', 'font', 'alignment', 'border', 'background', 'protection', ] for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, reg & 1) reg >>= 1 upkbits(xf.background, pkd_bkg_34, ( ( 0, 0x003F, 'fill_pattern'), ( 6, 0x07C0, 'pattern_colour_index'), (11, 0xF800, 'background_colour_index'), )) upkbitsL(xf.border, pkd_brd_34, ( ( 0, 0x00000007, 'top_line_style'), ( 3, 0x000000F8, 'top_colour_index'), ( 8, 0x00000700, 'left_line_style'), (11, 0x0000F800, 'left_colour_index'), (16, 0x00070000, 'bottom_line_style'), (19, 0x00F80000, 'bottom_colour_index'), (24, 0x07000000, 'right_line_style'), (27, 0xF8000000, 'right_colour_index'), )) xf.alignment.vert_align = 2 # bottom xf.alignment.rotation = 0 elif bv == 21: ## Warning: incomplete treatment; formatting_info not fully supported. ## Probably need to offset incoming BIFF2 XF[n] to BIFF8-like XF[n+16], ## and create XF[0:16] like the standard ones in BIFF8 *AND* add 16 to ## all XF references in cell records :-( (xf.font_index, format_etc, halign_etc) = unpack('= 3 blah1 = DEBUG or self.verbosity >= 1 if blah: fprintf(self.logfile, "xf_epilogue called ...\n") def check_same(book_arg, xf_arg, parent_arg, attr): # the _arg caper is to avoid a Warning msg from Python 2.1 :-( if getattr(xf_arg, attr) != getattr(parent_arg, attr): fprintf(book_arg.logfile, "NOTE !!! XF[%d] parent[%d] %s different\n", xf_arg.xf_index, parent_arg.xf_index, attr) for xfx in xrange(num_xfs): xf = self.xf_list[xfx] try: fmt = self.format_map[xf.format_key] cellty = _cellty_from_fmtty[fmt.type] except KeyError: cellty = XL_CELL_TEXT self._xf_index_to_xl_type_map[xf.xf_index] = cellty # Now for some assertions etc if not self.formatting_info: continue if xf.is_style: continue if not(0 <= xf.parent_style_index < num_xfs): if blah1: fprintf(self.logfile, "WARNING *** XF[%d]: is_style=%d but parent_style_index=%d\n", xf.xf_index, xf.is_style, xf.parent_style_index) # make it conform xf.parent_style_index = 0 if self.biff_version >= 30: if blah1: if xf.parent_style_index == xf.xf_index: fprintf(self.logfile, "NOTE !!! XF[%d]: parent_style_index is also %d\n", xf.xf_index, xf.parent_style_index) elif not self.xf_list[xf.parent_style_index].is_style: fprintf(self.logfile, "NOTE !!! XF[%d]: parent_style_index is %d; style flag not set\n", xf.xf_index, xf.parent_style_index) if blah1 and xf.parent_style_index > xf.xf_index: fprintf(self.logfile, "NOTE !!! XF[%d]: parent_style_index is %d; out of order?\n", xf.xf_index, xf.parent_style_index) parent = self.xf_list[xf.parent_style_index] if not xf._alignment_flag and not parent._alignment_flag: if blah1: check_same(self, xf, parent, 'alignment') if not xf._background_flag and not parent._background_flag: if blah1: check_same(self, xf, parent, 'background') if not xf._border_flag and not parent._border_flag: if blah1: check_same(self, xf, parent, 'border') if not xf._protection_flag and not parent._protection_flag: if blah1: check_same(self, xf, parent, 'protection') if not xf._format_flag and not parent._format_flag: if blah1 and xf.format_key != parent.format_key: fprintf(self.logfile, "NOTE !!! XF[%d] fmtk=%d, parent[%d] fmtk=%r\n%r / %r\n", xf.xf_index, xf.format_key, parent.xf_index, parent.format_key, self.format_map[xf.format_key].format_str, self.format_map[parent.format_key].format_str) if not xf._font_flag and not parent._font_flag: if blah1 and xf.font_index != parent.font_index: fprintf(self.logfile, "NOTE !!! XF[%d] fontx=%d, parent[%d] fontx=%r\n", xf.xf_index, xf.font_index, parent.xf_index, parent.font_index) def initialise_book(book): initialise_colour_map(book) book._xf_epilogue_done = 0 methods = ( handle_font, handle_efont, handle_format, is_date_format_string, handle_palette, palette_epilogue, handle_style, handle_xf, xf_epilogue, ) for method in methods: setattr(book.__class__, method.__name__, method) class XFBorder(BaseObject, EqNeAttrs): """ A collection of the border-related attributes of an ``XF`` record. Items correspond to those in the Excel UI's Format -> Cells -> Border tab. An explanations of "colour index" is given in :ref:`palette`. There are five line style attributes; possible values and the associated meanings are:: 0 = No line, 1 = Thin, 2 = Medium, 3 = Dashed, 4 = Dotted, 5 = Thick, 6 = Double, 7 = Hair, 8 = Medium dashed, 9 = Thin dash-dotted, 10 = Medium dash-dotted, 11 = Thin dash-dot-dotted, 12 = Medium dash-dot-dotted, 13 = Slanted medium dash-dotted. The line styles 8 to 13 appear in BIFF8 files (Excel 97 and later) only. For pictures of the line styles, refer to OOo docs s3.10 (p22) "Line Styles for Cell Borders (BIFF3-BIFF8)".

.. versionadded:: 0.6.1 """ #: The colour index for the cell's top line top_colour_index = 0 #: The colour index for the cell's bottom line bottom_colour_index = 0 #: The colour index for the cell's left line left_colour_index = 0 #: The colour index for the cell's right line right_colour_index = 0 #: The colour index for the cell's diagonal lines, if any diag_colour_index = 0 #: The line style for the cell's top line top_line_style = 0 #: The line style for the cell's bottom line bottom_line_style = 0 #: The line style for the cell's left line left_line_style = 0 #: The line style for the cell's right line right_line_style = 0 #: The line style for the cell's diagonal lines, if any diag_line_style = 0 #: 1 = draw a diagonal from top left to bottom right diag_down = 0 #: 1 = draw a diagonal from bottom left to top right diag_up = 0 class XFBackground(BaseObject, EqNeAttrs): """ A collection of the background-related attributes of an ``XF`` record. Items correspond to those in the Excel UI's Format -> Cells -> Patterns tab. An explanations of "colour index" is given in :ref:`palette`. .. versionadded:: 0.6.1 """ #: See section 3.11 of the OOo docs. fill_pattern = 0 #: See section 3.11 of the OOo docs. background_colour_index = 0 #: See section 3.11 of the OOo docs. pattern_colour_index = 0 class XFAlignment(BaseObject, EqNeAttrs): """ A collection of the alignment and similar attributes of an ``XF`` record. Items correspond to those in the Excel UI's Format -> Cells -> Alignment tab. .. versionadded:: 0.6.1 """ #: Values: section 6.115 (p 214) of OOo docs hor_align = 0 #: Values: section 6.115 (p 215) of OOo docs vert_align = 0 #: Values: section 6.115 (p 215) of OOo docs. #: #: .. note:: #: file versions BIFF7 and earlier use the documented #: :attr:`orientation` attribute; this will be mapped (without loss) #: into :attr:`rotation`. rotation = 0 #: 1 = text is wrapped at right margin text_wrapped = 0 #: A number in ``range(15)``. indent_level = 0 #: 1 = shrink font size to fit text into cell. shrink_to_fit = 0 #: 0 = according to context; 1 = left-to-right; 2 = right-to-left text_direction = 0 class XFProtection(BaseObject, EqNeAttrs): """ A collection of the protection-related attributes of an ``XF`` record. Items correspond to those in the Excel UI's Format -> Cells -> Protection tab. Note the OOo docs include the "cell or style" bit in this bundle of attributes. This is incorrect; the bit is used in determining which bundles to use. .. versionadded:: 0.6.1 """ #: 1 = Cell is prevented from being changed, moved, resized, or deleted #: (only if the sheet is protected). cell_locked = 0 #: 1 = Hide formula so that it doesn't appear in the formula bar when #: the cell is selected (only if the sheet is protected). formula_hidden = 0 class XF(BaseObject): """ eXtended Formatting information for cells, rows, columns and styles. Each of the 6 flags below describes the validity of a specific group of attributes. In cell XFs: - ``flag==0`` means the attributes of the parent style ``XF`` are used, (but only if the attributes are valid there); - ``flag==1`` means the attributes of this ``XF`` are used. In style XFs: - ``flag==0`` means the attribute setting is valid; - ``flag==1`` means the attribute should be ignored. .. note:: the API provides both "raw" XFs and "computed" XFs. In the latter case, cell XFs have had the above inheritance mechanism applied. .. versionadded:: 0.6.1 """ #: 0 = cell XF, 1 = style XF is_style = 0 #: cell XF: Index into Book.xf_list of this XF's style XF #: #: style XF: 0xFFF parent_style_index = 0 # _format_flag = 0 # _font_flag = 0 # _alignment_flag = 0 # _border_flag = 0 # _background_flag = 0 _protection_flag = 0 #: Index into :attr:`~xlrd.book.Book.xf_list` xf_index = 0 #: Index into :attr:`~xlrd.book.Book.font_list` font_index = 0 #: Key into :attr:`~xlrd.book.Book.format_map` #: #: .. warning:: #: OOo docs on the XF record call this "Index to FORMAT record". #: It is not an index in the Python sense. It is a key to a map. #: It is true *only* for Excel 4.0 and earlier files #: that the key into format_map from an XF instance #: is the same as the index into format_list, and *only* #: if the index is less than 164. format_key = 0 #: An instance of an :class:`XFProtection` object. protection = None #: An instance of an :class:`XFBackground` object. background = None #: An instance of an :class:`XFAlignment` object. alignment = None #: An instance of an :class:`XFBorder` object. border = None xlrd-2.0.1/xlrd/formula.py000066400000000000000000002703671376464300000154750ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2005-2012 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. # No part of the content of this file was derived from the works of # David Giffin. """ Module for parsing/evaluating Microsoft Excel formulas. """ from __future__ import print_function import copy import operator as opr from struct import unpack from .biffh import ( BaseObject, XLRDError, error_text_from_code, hex_char_dump, unpack_string_update_pos, unpack_unicode_update_pos, ) from .timemachine import * __all__ = [ 'oBOOL', 'oERR', 'oNUM', 'oREF', 'oREL', 'oSTRG', 'oUNK', 'decompile_formula', 'dump_formula', 'evaluate_name_formula', 'okind_dict', 'rangename3d', 'rangename3drel', 'cellname', 'cellnameabs', 'colname', 'FMLA_TYPE_CELL', 'FMLA_TYPE_SHARED', 'FMLA_TYPE_ARRAY', 'FMLA_TYPE_COND_FMT', 'FMLA_TYPE_DATA_VAL', 'FMLA_TYPE_NAME', 'Operand', 'Ref3D', ] FMLA_TYPE_CELL = 1 FMLA_TYPE_SHARED = 2 FMLA_TYPE_ARRAY = 4 FMLA_TYPE_COND_FMT = 8 FMLA_TYPE_DATA_VAL = 16 FMLA_TYPE_NAME = 32 ALL_FMLA_TYPES = 63 FMLA_TYPEDESCR_MAP = { 1 : 'CELL', 2 : 'SHARED', 4 : 'ARRAY', 8 : 'COND-FMT', 16: 'DATA-VAL', 32: 'NAME', } _TOKEN_NOT_ALLOWED = { 0x01: ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tExp 0x02: ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tTbl 0x0F: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tIsect 0x10: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tUnion/List 0x11: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRange 0x20: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArray 0x23: FMLA_TYPE_SHARED, # tName 0x39: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tNameX 0x3A: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tRef3d 0x3B: FMLA_TYPE_SHARED + FMLA_TYPE_COND_FMT + FMLA_TYPE_DATA_VAL, # tArea3d 0x2C: FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tRefN 0x2D: FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tAreaN # plus weird stuff like tMem* }.get oBOOL = 3 oERR = 4 oMSNG = 5 # tMissArg oNUM = 2 oREF = -1 oREL = -2 oSTRG = 1 oUNK = 0 okind_dict = { -2: "oREL", -1: "oREF", 0 : "oUNK", 1 : "oSTRG", 2 : "oNUM", 3 : "oBOOL", 4 : "oERR", 5 : "oMSNG", } listsep = ',' #### probably should depend on locale # sztabN[opcode] -> the number of bytes to consume. # -1 means variable # -2 means this opcode not implemented in this version. # Which N to use? Depends on biff_version; see szdict. sztab0 = [-2, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 8, 4, 2, 2, 3, 9, 8, 2, 3, 8, 4, 7, 5, 5, 5, 2, 4, 7, 4, 7, 2, 2, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2] sztab1 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 9, 2, 3, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, 3, -2, -2, -2, -2, -2, -2, -2] sztab2 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, 11, 5, 2, 2, 3, 9, 9, 3, 4, 11, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2] sztab3 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -2, -1, -2, -2, 2, 2, 3, 9, 9, 3, 4, 15, 4, 7, 7, 7, 7, 3, 4, 7, 4, 7, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 25, 18, 21, 18, 21, -2, -2] sztab4 = [-2, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -2, -2, 2, 2, 3, 9, 9, 3, 4, 5, 5, 9, 7, 7, 7, 3, 5, 9, 5, 9, 3, 3, -2, -2, -2, -2, -2, -2, -2, -2, -2, 7, 7, 11, 7, 11, -2, -2] szdict = { 20 : sztab0, 21 : sztab0, 30 : sztab1, 40 : sztab2, 45 : sztab2, 50 : sztab3, 70 : sztab3, 80 : sztab4, } # For debugging purposes ... the name for each opcode # (without the prefix "t" used on OOo docs) onames = ['Unk00', 'Exp', 'Tbl', 'Add', 'Sub', 'Mul', 'Div', 'Power', 'Concat', 'LT', 'LE', 'EQ', 'GE', 'GT', 'NE', 'Isect', 'List', 'Range', 'Uplus', 'Uminus', 'Percent', 'Paren', 'MissArg', 'Str', 'Extended', 'Attr', 'Sheet', 'EndSheet', 'Err', 'Bool', 'Int', 'Num', 'Array', 'Func', 'FuncVar', 'Name', 'Ref', 'Area', 'MemArea', 'MemErr', 'MemNoMem', 'MemFunc', 'RefErr', 'AreaErr', 'RefN', 'AreaN', 'MemAreaN', 'MemNoMemN', '', '', '', '', '', '', '', '', 'FuncCE', 'NameX', 'Ref3d', 'Area3d', 'RefErr3d', 'AreaErr3d', '', ''] func_defs = { # index: (name, min#args, max#args, flags, #known_args, return_type, kargs) 0 : ('COUNT', 0, 30, 0x04, 1, 'V', 'R'), 1 : ('IF', 2, 3, 0x04, 3, 'V', 'VRR'), 2 : ('ISNA', 1, 1, 0x02, 1, 'V', 'V'), 3 : ('ISERROR', 1, 1, 0x02, 1, 'V', 'V'), 4 : ('SUM', 0, 30, 0x04, 1, 'V', 'R'), 5 : ('AVERAGE', 1, 30, 0x04, 1, 'V', 'R'), 6 : ('MIN', 1, 30, 0x04, 1, 'V', 'R'), 7 : ('MAX', 1, 30, 0x04, 1, 'V', 'R'), 8 : ('ROW', 0, 1, 0x04, 1, 'V', 'R'), 9 : ('COLUMN', 0, 1, 0x04, 1, 'V', 'R'), 10 : ('NA', 0, 0, 0x02, 0, 'V', ''), 11 : ('NPV', 2, 30, 0x04, 2, 'V', 'VR'), 12 : ('STDEV', 1, 30, 0x04, 1, 'V', 'R'), 13 : ('DOLLAR', 1, 2, 0x04, 1, 'V', 'V'), 14 : ('FIXED', 2, 3, 0x04, 3, 'V', 'VVV'), 15 : ('SIN', 1, 1, 0x02, 1, 'V', 'V'), 16 : ('COS', 1, 1, 0x02, 1, 'V', 'V'), 17 : ('TAN', 1, 1, 0x02, 1, 'V', 'V'), 18 : ('ATAN', 1, 1, 0x02, 1, 'V', 'V'), 19 : ('PI', 0, 0, 0x02, 0, 'V', ''), 20 : ('SQRT', 1, 1, 0x02, 1, 'V', 'V'), 21 : ('EXP', 1, 1, 0x02, 1, 'V', 'V'), 22 : ('LN', 1, 1, 0x02, 1, 'V', 'V'), 23 : ('LOG10', 1, 1, 0x02, 1, 'V', 'V'), 24 : ('ABS', 1, 1, 0x02, 1, 'V', 'V'), 25 : ('INT', 1, 1, 0x02, 1, 'V', 'V'), 26 : ('SIGN', 1, 1, 0x02, 1, 'V', 'V'), 27 : ('ROUND', 2, 2, 0x02, 2, 'V', 'VV'), 28 : ('LOOKUP', 2, 3, 0x04, 2, 'V', 'VR'), 29 : ('INDEX', 2, 4, 0x0c, 4, 'R', 'RVVV'), 30 : ('REPT', 2, 2, 0x02, 2, 'V', 'VV'), 31 : ('MID', 3, 3, 0x02, 3, 'V', 'VVV'), 32 : ('LEN', 1, 1, 0x02, 1, 'V', 'V'), 33 : ('VALUE', 1, 1, 0x02, 1, 'V', 'V'), 34 : ('TRUE', 0, 0, 0x02, 0, 'V', ''), 35 : ('FALSE', 0, 0, 0x02, 0, 'V', ''), 36 : ('AND', 1, 30, 0x04, 1, 'V', 'R'), 37 : ('OR', 1, 30, 0x04, 1, 'V', 'R'), 38 : ('NOT', 1, 1, 0x02, 1, 'V', 'V'), 39 : ('MOD', 2, 2, 0x02, 2, 'V', 'VV'), 40 : ('DCOUNT', 3, 3, 0x02, 3, 'V', 'RRR'), 41 : ('DSUM', 3, 3, 0x02, 3, 'V', 'RRR'), 42 : ('DAVERAGE', 3, 3, 0x02, 3, 'V', 'RRR'), 43 : ('DMIN', 3, 3, 0x02, 3, 'V', 'RRR'), 44 : ('DMAX', 3, 3, 0x02, 3, 'V', 'RRR'), 45 : ('DSTDEV', 3, 3, 0x02, 3, 'V', 'RRR'), 46 : ('VAR', 1, 30, 0x04, 1, 'V', 'R'), 47 : ('DVAR', 3, 3, 0x02, 3, 'V', 'RRR'), 48 : ('TEXT', 2, 2, 0x02, 2, 'V', 'VV'), 49 : ('LINEST', 1, 4, 0x04, 4, 'A', 'RRVV'), 50 : ('TREND', 1, 4, 0x04, 4, 'A', 'RRRV'), 51 : ('LOGEST', 1, 4, 0x04, 4, 'A', 'RRVV'), 52 : ('GROWTH', 1, 4, 0x04, 4, 'A', 'RRRV'), 56 : ('PV', 3, 5, 0x04, 5, 'V', 'VVVVV'), 57 : ('FV', 3, 5, 0x04, 5, 'V', 'VVVVV'), 58 : ('NPER', 3, 5, 0x04, 5, 'V', 'VVVVV'), 59 : ('PMT', 3, 5, 0x04, 5, 'V', 'VVVVV'), 60 : ('RATE', 3, 6, 0x04, 6, 'V', 'VVVVVV'), 61 : ('MIRR', 3, 3, 0x02, 3, 'V', 'RVV'), 62 : ('IRR', 1, 2, 0x04, 2, 'V', 'RV'), 63 : ('RAND', 0, 0, 0x0a, 0, 'V', ''), 64 : ('MATCH', 2, 3, 0x04, 3, 'V', 'VRR'), 65 : ('DATE', 3, 3, 0x02, 3, 'V', 'VVV'), 66 : ('TIME', 3, 3, 0x02, 3, 'V', 'VVV'), 67 : ('DAY', 1, 1, 0x02, 1, 'V', 'V'), 68 : ('MONTH', 1, 1, 0x02, 1, 'V', 'V'), 69 : ('YEAR', 1, 1, 0x02, 1, 'V', 'V'), 70 : ('WEEKDAY', 1, 2, 0x04, 2, 'V', 'VV'), 71 : ('HOUR', 1, 1, 0x02, 1, 'V', 'V'), 72 : ('MINUTE', 1, 1, 0x02, 1, 'V', 'V'), 73 : ('SECOND', 1, 1, 0x02, 1, 'V', 'V'), 74 : ('NOW', 0, 0, 0x0a, 0, 'V', ''), 75 : ('AREAS', 1, 1, 0x02, 1, 'V', 'R'), 76 : ('ROWS', 1, 1, 0x02, 1, 'V', 'R'), 77 : ('COLUMNS', 1, 1, 0x02, 1, 'V', 'R'), 78 : ('OFFSET', 3, 5, 0x04, 5, 'R', 'RVVVV'), 82 : ('SEARCH', 2, 3, 0x04, 3, 'V', 'VVV'), 83 : ('TRANSPOSE', 1, 1, 0x02, 1, 'A', 'A'), 86 : ('TYPE', 1, 1, 0x02, 1, 'V', 'V'), 92 : ('SERIESSUM', 4, 4, 0x02, 4, 'V', 'VVVA'), 97 : ('ATAN2', 2, 2, 0x02, 2, 'V', 'VV'), 98 : ('ASIN', 1, 1, 0x02, 1, 'V', 'V'), 99 : ('ACOS', 1, 1, 0x02, 1, 'V', 'V'), 100: ('CHOOSE', 2, 30, 0x04, 2, 'V', 'VR'), 101: ('HLOOKUP', 3, 4, 0x04, 4, 'V', 'VRRV'), 102: ('VLOOKUP', 3, 4, 0x04, 4, 'V', 'VRRV'), 105: ('ISREF', 1, 1, 0x02, 1, 'V', 'R'), 109: ('LOG', 1, 2, 0x04, 2, 'V', 'VV'), 111: ('CHAR', 1, 1, 0x02, 1, 'V', 'V'), 112: ('LOWER', 1, 1, 0x02, 1, 'V', 'V'), 113: ('UPPER', 1, 1, 0x02, 1, 'V', 'V'), 114: ('PROPER', 1, 1, 0x02, 1, 'V', 'V'), 115: ('LEFT', 1, 2, 0x04, 2, 'V', 'VV'), 116: ('RIGHT', 1, 2, 0x04, 2, 'V', 'VV'), 117: ('EXACT', 2, 2, 0x02, 2, 'V', 'VV'), 118: ('TRIM', 1, 1, 0x02, 1, 'V', 'V'), 119: ('REPLACE', 4, 4, 0x02, 4, 'V', 'VVVV'), 120: ('SUBSTITUTE', 3, 4, 0x04, 4, 'V', 'VVVV'), 121: ('CODE', 1, 1, 0x02, 1, 'V', 'V'), 124: ('FIND', 2, 3, 0x04, 3, 'V', 'VVV'), 125: ('CELL', 1, 2, 0x0c, 2, 'V', 'VR'), 126: ('ISERR', 1, 1, 0x02, 1, 'V', 'V'), 127: ('ISTEXT', 1, 1, 0x02, 1, 'V', 'V'), 128: ('ISNUMBER', 1, 1, 0x02, 1, 'V', 'V'), 129: ('ISBLANK', 1, 1, 0x02, 1, 'V', 'V'), 130: ('T', 1, 1, 0x02, 1, 'V', 'R'), 131: ('N', 1, 1, 0x02, 1, 'V', 'R'), 140: ('DATEVALUE', 1, 1, 0x02, 1, 'V', 'V'), 141: ('TIMEVALUE', 1, 1, 0x02, 1, 'V', 'V'), 142: ('SLN', 3, 3, 0x02, 3, 'V', 'VVV'), 143: ('SYD', 4, 4, 0x02, 4, 'V', 'VVVV'), 144: ('DDB', 4, 5, 0x04, 5, 'V', 'VVVVV'), 148: ('INDIRECT', 1, 2, 0x0c, 2, 'R', 'VV'), 162: ('CLEAN', 1, 1, 0x02, 1, 'V', 'V'), 163: ('MDETERM', 1, 1, 0x02, 1, 'V', 'A'), 164: ('MINVERSE', 1, 1, 0x02, 1, 'A', 'A'), 165: ('MMULT', 2, 2, 0x02, 2, 'A', 'AA'), 167: ('IPMT', 4, 6, 0x04, 6, 'V', 'VVVVVV'), 168: ('PPMT', 4, 6, 0x04, 6, 'V', 'VVVVVV'), 169: ('COUNTA', 0, 30, 0x04, 1, 'V', 'R'), 183: ('PRODUCT', 0, 30, 0x04, 1, 'V', 'R'), 184: ('FACT', 1, 1, 0x02, 1, 'V', 'V'), 189: ('DPRODUCT', 3, 3, 0x02, 3, 'V', 'RRR'), 190: ('ISNONTEXT', 1, 1, 0x02, 1, 'V', 'V'), 193: ('STDEVP', 1, 30, 0x04, 1, 'V', 'R'), 194: ('VARP', 1, 30, 0x04, 1, 'V', 'R'), 195: ('DSTDEVP', 3, 3, 0x02, 3, 'V', 'RRR'), 196: ('DVARP', 3, 3, 0x02, 3, 'V', 'RRR'), 197: ('TRUNC', 1, 2, 0x04, 2, 'V', 'VV'), 198: ('ISLOGICAL', 1, 1, 0x02, 1, 'V', 'V'), 199: ('DCOUNTA', 3, 3, 0x02, 3, 'V', 'RRR'), 204: ('USDOLLAR', 1, 2, 0x04, 2, 'V', 'VV'), 205: ('FINDB', 2, 3, 0x04, 3, 'V', 'VVV'), 206: ('SEARCHB', 2, 3, 0x04, 3, 'V', 'VVV'), 207: ('REPLACEB', 4, 4, 0x02, 4, 'V', 'VVVV'), 208: ('LEFTB', 1, 2, 0x04, 2, 'V', 'VV'), 209: ('RIGHTB', 1, 2, 0x04, 2, 'V', 'VV'), 210: ('MIDB', 3, 3, 0x02, 3, 'V', 'VVV'), 211: ('LENB', 1, 1, 0x02, 1, 'V', 'V'), 212: ('ROUNDUP', 2, 2, 0x02, 2, 'V', 'VV'), 213: ('ROUNDDOWN', 2, 2, 0x02, 2, 'V', 'VV'), 214: ('ASC', 1, 1, 0x02, 1, 'V', 'V'), 215: ('DBCS', 1, 1, 0x02, 1, 'V', 'V'), 216: ('RANK', 2, 3, 0x04, 3, 'V', 'VRV'), 219: ('ADDRESS', 2, 5, 0x04, 5, 'V', 'VVVVV'), 220: ('DAYS360', 2, 3, 0x04, 3, 'V', 'VVV'), 221: ('TODAY', 0, 0, 0x0a, 0, 'V', ''), 222: ('VDB', 5, 7, 0x04, 7, 'V', 'VVVVVVV'), 227: ('MEDIAN', 1, 30, 0x04, 1, 'V', 'R'), 228: ('SUMPRODUCT', 1, 30, 0x04, 1, 'V', 'A'), 229: ('SINH', 1, 1, 0x02, 1, 'V', 'V'), 230: ('COSH', 1, 1, 0x02, 1, 'V', 'V'), 231: ('TANH', 1, 1, 0x02, 1, 'V', 'V'), 232: ('ASINH', 1, 1, 0x02, 1, 'V', 'V'), 233: ('ACOSH', 1, 1, 0x02, 1, 'V', 'V'), 234: ('ATANH', 1, 1, 0x02, 1, 'V', 'V'), 235: ('DGET', 3, 3, 0x02, 3, 'V', 'RRR'), 244: ('INFO', 1, 1, 0x02, 1, 'V', 'V'), 247: ('DB', 4, 5, 0x04, 5, 'V', 'VVVVV'), 252: ('FREQUENCY', 2, 2, 0x02, 2, 'A', 'RR'), 261: ('ERROR.TYPE', 1, 1, 0x02, 1, 'V', 'V'), 269: ('AVEDEV', 1, 30, 0x04, 1, 'V', 'R'), 270: ('BETADIST', 3, 5, 0x04, 1, 'V', 'V'), 271: ('GAMMALN', 1, 1, 0x02, 1, 'V', 'V'), 272: ('BETAINV', 3, 5, 0x04, 1, 'V', 'V'), 273: ('BINOMDIST', 4, 4, 0x02, 4, 'V', 'VVVV'), 274: ('CHIDIST', 2, 2, 0x02, 2, 'V', 'VV'), 275: ('CHIINV', 2, 2, 0x02, 2, 'V', 'VV'), 276: ('COMBIN', 2, 2, 0x02, 2, 'V', 'VV'), 277: ('CONFIDENCE', 3, 3, 0x02, 3, 'V', 'VVV'), 278: ('CRITBINOM', 3, 3, 0x02, 3, 'V', 'VVV'), 279: ('EVEN', 1, 1, 0x02, 1, 'V', 'V'), 280: ('EXPONDIST', 3, 3, 0x02, 3, 'V', 'VVV'), 281: ('FDIST', 3, 3, 0x02, 3, 'V', 'VVV'), 282: ('FINV', 3, 3, 0x02, 3, 'V', 'VVV'), 283: ('FISHER', 1, 1, 0x02, 1, 'V', 'V'), 284: ('FISHERINV', 1, 1, 0x02, 1, 'V', 'V'), 285: ('FLOOR', 2, 2, 0x02, 2, 'V', 'VV'), 286: ('GAMMADIST', 4, 4, 0x02, 4, 'V', 'VVVV'), 287: ('GAMMAINV', 3, 3, 0x02, 3, 'V', 'VVV'), 288: ('CEILING', 2, 2, 0x02, 2, 'V', 'VV'), 289: ('HYPGEOMDIST', 4, 4, 0x02, 4, 'V', 'VVVV'), 290: ('LOGNORMDIST', 3, 3, 0x02, 3, 'V', 'VVV'), 291: ('LOGINV', 3, 3, 0x02, 3, 'V', 'VVV'), 292: ('NEGBINOMDIST', 3, 3, 0x02, 3, 'V', 'VVV'), 293: ('NORMDIST', 4, 4, 0x02, 4, 'V', 'VVVV'), 294: ('NORMSDIST', 1, 1, 0x02, 1, 'V', 'V'), 295: ('NORMINV', 3, 3, 0x02, 3, 'V', 'VVV'), 296: ('NORMSINV', 1, 1, 0x02, 1, 'V', 'V'), 297: ('STANDARDIZE', 3, 3, 0x02, 3, 'V', 'VVV'), 298: ('ODD', 1, 1, 0x02, 1, 'V', 'V'), 299: ('PERMUT', 2, 2, 0x02, 2, 'V', 'VV'), 300: ('POISSON', 3, 3, 0x02, 3, 'V', 'VVV'), 301: ('TDIST', 3, 3, 0x02, 3, 'V', 'VVV'), 302: ('WEIBULL', 4, 4, 0x02, 4, 'V', 'VVVV'), 303: ('SUMXMY2', 2, 2, 0x02, 2, 'V', 'AA'), 304: ('SUMX2MY2', 2, 2, 0x02, 2, 'V', 'AA'), 305: ('SUMX2PY2', 2, 2, 0x02, 2, 'V', 'AA'), 306: ('CHITEST', 2, 2, 0x02, 2, 'V', 'AA'), 307: ('CORREL', 2, 2, 0x02, 2, 'V', 'AA'), 308: ('COVAR', 2, 2, 0x02, 2, 'V', 'AA'), 309: ('FORECAST', 3, 3, 0x02, 3, 'V', 'VAA'), 310: ('FTEST', 2, 2, 0x02, 2, 'V', 'AA'), 311: ('INTERCEPT', 2, 2, 0x02, 2, 'V', 'AA'), 312: ('PEARSON', 2, 2, 0x02, 2, 'V', 'AA'), 313: ('RSQ', 2, 2, 0x02, 2, 'V', 'AA'), 314: ('STEYX', 2, 2, 0x02, 2, 'V', 'AA'), 315: ('SLOPE', 2, 2, 0x02, 2, 'V', 'AA'), 316: ('TTEST', 4, 4, 0x02, 4, 'V', 'AAVV'), 317: ('PROB', 3, 4, 0x04, 3, 'V', 'AAV'), 318: ('DEVSQ', 1, 30, 0x04, 1, 'V', 'R'), 319: ('GEOMEAN', 1, 30, 0x04, 1, 'V', 'R'), 320: ('HARMEAN', 1, 30, 0x04, 1, 'V', 'R'), 321: ('SUMSQ', 0, 30, 0x04, 1, 'V', 'R'), 322: ('KURT', 1, 30, 0x04, 1, 'V', 'R'), 323: ('SKEW', 1, 30, 0x04, 1, 'V', 'R'), 324: ('ZTEST', 2, 3, 0x04, 2, 'V', 'RV'), 325: ('LARGE', 2, 2, 0x02, 2, 'V', 'RV'), 326: ('SMALL', 2, 2, 0x02, 2, 'V', 'RV'), 327: ('QUARTILE', 2, 2, 0x02, 2, 'V', 'RV'), 328: ('PERCENTILE', 2, 2, 0x02, 2, 'V', 'RV'), 329: ('PERCENTRANK', 2, 3, 0x04, 2, 'V', 'RV'), 330: ('MODE', 1, 30, 0x04, 1, 'V', 'A'), 331: ('TRIMMEAN', 2, 2, 0x02, 2, 'V', 'RV'), 332: ('TINV', 2, 2, 0x02, 2, 'V', 'VV'), 336: ('CONCATENATE', 0, 30, 0x04, 1, 'V', 'V'), 337: ('POWER', 2, 2, 0x02, 2, 'V', 'VV'), 342: ('RADIANS', 1, 1, 0x02, 1, 'V', 'V'), 343: ('DEGREES', 1, 1, 0x02, 1, 'V', 'V'), 344: ('SUBTOTAL', 2, 30, 0x04, 2, 'V', 'VR'), 345: ('SUMIF', 2, 3, 0x04, 3, 'V', 'RVR'), 346: ('COUNTIF', 2, 2, 0x02, 2, 'V', 'RV'), 347: ('COUNTBLANK', 1, 1, 0x02, 1, 'V', 'R'), 350: ('ISPMT', 4, 4, 0x02, 4, 'V', 'VVVV'), 351: ('DATEDIF', 3, 3, 0x02, 3, 'V', 'VVV'), 352: ('DATESTRING', 1, 1, 0x02, 1, 'V', 'V'), 353: ('NUMBERSTRING', 2, 2, 0x02, 2, 'V', 'VV'), 354: ('ROMAN', 1, 2, 0x04, 2, 'V', 'VV'), 358: ('GETPIVOTDATA', 2, 2, 0x02, 2, 'V', 'RV'), 359: ('HYPERLINK', 1, 2, 0x04, 2, 'V', 'VV'), 360: ('PHONETIC', 1, 1, 0x02, 1, 'V', 'V'), 361: ('AVERAGEA', 1, 30, 0x04, 1, 'V', 'R'), 362: ('MAXA', 1, 30, 0x04, 1, 'V', 'R'), 363: ('MINA', 1, 30, 0x04, 1, 'V', 'R'), 364: ('STDEVPA', 1, 30, 0x04, 1, 'V', 'R'), 365: ('VARPA', 1, 30, 0x04, 1, 'V', 'R'), 366: ('STDEVA', 1, 30, 0x04, 1, 'V', 'R'), 367: ('VARA', 1, 30, 0x04, 1, 'V', 'R'), 368: ('BAHTTEXT', 1, 1, 0x02, 1, 'V', 'V'), 369: ('THAIDAYOFWEEK', 1, 1, 0x02, 1, 'V', 'V'), 370: ('THAIDIGIT', 1, 1, 0x02, 1, 'V', 'V'), 371: ('THAIMONTHOFYEAR', 1, 1, 0x02, 1, 'V', 'V'), 372: ('THAINUMSOUND', 1, 1, 0x02, 1, 'V', 'V'), 373: ('THAINUMSTRING', 1, 1, 0x02, 1, 'V', 'V'), 374: ('THAISTRINGLENGTH', 1, 1, 0x02, 1, 'V', 'V'), 375: ('ISTHAIDIGIT', 1, 1, 0x02, 1, 'V', 'V'), 376: ('ROUNDBAHTDOWN', 1, 1, 0x02, 1, 'V', 'V'), 377: ('ROUNDBAHTUP', 1, 1, 0x02, 1, 'V', 'V'), 378: ('THAIYEAR', 1, 1, 0x02, 1, 'V', 'V'), 379: ('RTD', 2, 5, 0x04, 1, 'V', 'V'), } tAttrNames = { 0x00: "Skip??", # seen in SAMPLES.XLS which shipped with Excel 5.0 0x01: "Volatile", 0x02: "If", 0x04: "Choose", 0x08: "Skip", 0x10: "Sum", 0x20: "Assign", 0x40: "Space", 0x41: "SpaceVolatile", } error_opcodes = set([0x07, 0x08, 0x0A, 0x0B, 0x1C, 0x1D, 0x2F]) tRangeFuncs = (min, max, min, max, min, max) tIsectFuncs = (max, min, max, min, max, min) def do_box_funcs(box_funcs, boxa, boxb): return tuple( func(numa, numb) for func, numa, numb in zip(box_funcs, boxa.coords, boxb.coords) ) def adjust_cell_addr_biff8(rowval, colval, reldelta, browx=None, bcolx=None): row_rel = (colval >> 15) & 1 col_rel = (colval >> 14) & 1 rowx = rowval colx = colval & 0xff if reldelta: if row_rel and rowx >= 32768: rowx -= 65536 if col_rel and colx >= 128: colx -= 256 else: if row_rel: rowx -= browx if col_rel: colx -= bcolx return rowx, colx, row_rel, col_rel def adjust_cell_addr_biff_le7( rowval, colval, reldelta, browx=None, bcolx=None): row_rel = (rowval >> 15) & 1 col_rel = (rowval >> 14) & 1 rowx = rowval & 0x3fff colx = colval if reldelta: if row_rel and rowx >= 8192: rowx -= 16384 if col_rel and colx >= 128: colx -= 256 else: if row_rel: rowx -= browx if col_rel: colx -= bcolx return rowx, colx, row_rel, col_rel def get_cell_addr(data, pos, bv, reldelta, browx=None, bcolx=None): if bv >= 80: rowval, colval = unpack("= 80: row1val, row2val, col1val, col2val = unpack(" addins %r" % (refx, info), file=bk.logfile) assert ref_first_sheetx == 0xFFFE == ref_last_sheetx return (-5, -5) if ref_recordx != bk._supbook_locals_inx: if blah: print("/// get_externsheet_local_range(refx=%d) -> external %r" % (refx, info), file=bk.logfile) return (-4, -4) # external reference if ref_first_sheetx == 0xFFFE == ref_last_sheetx: if blah: print("/// get_externsheet_local_range(refx=%d) -> unspecified sheet %r" % (refx, info), file=bk.logfile) return (-1, -1) # internal reference, any sheet if ref_first_sheetx == 0xFFFF == ref_last_sheetx: if blah: print("/// get_externsheet_local_range(refx=%d) -> deleted sheet(s)" % (refx, ), file=bk.logfile) return (-2, -2) # internal reference, deleted sheet(s) nsheets = len(bk._all_sheets_map) if not(0 <= ref_first_sheetx <= ref_last_sheetx < nsheets): if blah: print("/// get_externsheet_local_range(refx=%d) -> %r" % (refx, info), file=bk.logfile) print("--- first/last sheet not in range(%d)" % nsheets, file=bk.logfile) return (-102, -102) # stuffed up somewhere :-( xlrd_sheetx1 = bk._all_sheets_map[ref_first_sheetx] xlrd_sheetx2 = bk._all_sheets_map[ref_last_sheetx] if not(0 <= xlrd_sheetx1 <= xlrd_sheetx2): return (-3, -3) # internal reference, but to a macro sheet return xlrd_sheetx1, xlrd_sheetx2 def get_externsheet_local_range_b57( bk, raw_extshtx, ref_first_sheetx, ref_last_sheetx, blah=0): if raw_extshtx > 0: if blah: print("/// get_externsheet_local_range_b57(raw_extshtx=%d) -> external" % raw_extshtx, file=bk.logfile) return (-4, -4) # external reference if ref_first_sheetx == -1 and ref_last_sheetx == -1: return (-2, -2) # internal reference, deleted sheet(s) nsheets = len(bk._all_sheets_map) if not(0 <= ref_first_sheetx <= ref_last_sheetx < nsheets): if blah: print("/// get_externsheet_local_range_b57(%d, %d, %d) -> ???" % (raw_extshtx, ref_first_sheetx, ref_last_sheetx), file=bk.logfile) print("--- first/last sheet not in range(%d)" % nsheets, file=bk.logfile) return (-103, -103) # stuffed up somewhere :-( xlrd_sheetx1 = bk._all_sheets_map[ref_first_sheetx] xlrd_sheetx2 = bk._all_sheets_map[ref_last_sheetx] if not(0 <= xlrd_sheetx1 <= xlrd_sheetx2): return (-3, -3) # internal reference, but to a macro sheet return xlrd_sheetx1, xlrd_sheetx2 class FormulaError(Exception): pass class Operand(object): """ Used in evaluating formulas. The following table describes the kinds and how their values are represented. .. raw:: html
Kind symbol Kind number Value representation
oBOOL 3 integer: 0 => False; 1 => True
oERR 4 None, or an int error code (same as XL_CELL_ERROR in the Cell class).
oMSNG 5 Used by Excel as a placeholder for a missing (not supplied) function argument. Should *not* appear as a final formula result. Value is None.
oNUM 2 A float. Note that there is no way of distinguishing dates.
oREF -1 The value is either None or a non-empty list of absolute Ref3D instances.
oREL -2 The value is None or a non-empty list of fully or partially relative Ref3D instances.
oSTRG 1 A Unicode string.
oUNK 0 The kind is unknown or ambiguous. The value is None
""" #: None means that the actual value of the operand is a variable #: (depends on cell data), not a constant. value = None #: oUNK means that the kind of operand is not known unambiguously. kind = oUNK #: The reconstituted text of the original formula. Function names will be #: in English irrespective of the original language, which doesn't seem #: to be recorded anywhere. The separator is ",", not ";" or whatever else #: might be more appropriate for the end-user's locale; patches welcome. text = '?' def __init__(self, akind=None, avalue=None, arank=0, atext='?'): if akind is not None: self.kind = akind if avalue is not None: self.value = avalue self.rank = arank # rank is an internal gizmo (operator precedence); # it's used in reconstructing formula text. self.text = atext def __repr__(self): kind_text = okind_dict.get(self.kind, "?Unknown kind?") return "Operand(kind=%s, value=%r, text=%r)" \ % (kind_text, self.value, self.text) class Ref3D(tuple): """ Represents an absolute or relative 3-dimensional reference to a box of one or more cells. The ``coords`` attribute is a tuple of the form:: (shtxlo, shtxhi, rowxlo, rowxhi, colxlo, colxhi) where ``0 <= thingxlo <= thingx < thingxhi``. .. note:: It is quite possible to have ``thingx > nthings``; for example ``Print_Titles`` could have ``colxhi == 256`` and/or ``rowxhi == 65536`` irrespective of how many columns/rows are actually used in the worksheet. The caller will need to decide how to handle this situation. Keyword: :class:`IndexError` :-) The components of the coords attribute are also available as individual attributes: ``shtxlo``, ``shtxhi``, ``rowxlo``, ``rowxhi``, ``colxlo``, and ``colxhi``. The ``relflags`` attribute is a 6-tuple of flags which indicate whether the corresponding (sheet|row|col)(lo|hi) is relative (1) or absolute (0). .. note:: There is necessarily no information available as to what cell(s) the reference could possibly be relative to. The caller must decide what if any use to make of ``oREL`` operands. .. note: A partially relative reference may well be a typo. For example, define name ``A1Z10`` as ``$a$1:$z10`` (missing ``$`` after ``z``) while the cursor is on cell ``Sheet3!A27``. The resulting :class:`Ref3D` instance will have ``coords = (2, 3, 0, -16, 0, 26)`` and ``relflags = (0, 0, 0, 1, 0, 0).
So far, only one possibility of a sheet-relative component in a reference has been noticed: a 2D reference located in the "current sheet". This will appear as ``coords = (0, 1, ...)`` and ``relflags = (1, 1, ...)``. .. versionadded:: 0.6.0 """ def __init__(self, atuple): self.coords = atuple[0:6] self.relflags = atuple[6:12] if not self.relflags: self.relflags = (0, 0, 0, 0, 0, 0) (self.shtxlo, self.shtxhi, self.rowxlo, self.rowxhi, self.colxlo, self.colxhi) = self.coords def __repr__(self): if not self.relflags or self.relflags == (0, 0, 0, 0, 0, 0): return "Ref3D(coords=%r)" % (self.coords, ) else: return "Ref3D(coords=%r, relflags=%r)" \ % (self.coords, self.relflags) tAdd = 0x03 tSub = 0x04 tMul = 0x05 tDiv = 0x06 tPower = 0x07 tConcat = 0x08 tLT, tLE, tEQ, tGE, tGT, tNE = range(0x09, 0x0F) def nop(x): return x def _opr_pow(x, y): return x ** y def _opr_lt(x, y): return x < y def _opr_le(x, y): return x <= y def _opr_eq(x, y): return x == y def _opr_ge(x, y): return x >= y def _opr_gt(x, y): return x > y def _opr_ne(x, y): return x != y def num2strg(num): """ Attempt to emulate Excel's default conversion from number to string. """ s = str(num) if s.endswith(".0"): s = s[:-2] return s _arith_argdict = {oNUM: nop, oSTRG: float} _cmp_argdict = {oNUM: nop, oSTRG: nop} # Seems no conversions done on relops; in Excel, "1" > 9 produces TRUE. _strg_argdict = {oNUM:num2strg, oSTRG:nop} binop_rules = { tAdd: (_arith_argdict, oNUM, opr.add, 30, '+'), tSub: (_arith_argdict, oNUM, opr.sub, 30, '-'), tMul: (_arith_argdict, oNUM, opr.mul, 40, '*'), tDiv: (_arith_argdict, oNUM, opr.truediv, 40, '/'), tPower: (_arith_argdict, oNUM, _opr_pow, 50, '^',), tConcat:(_strg_argdict, oSTRG, opr.add, 20, '&'), tLT: (_cmp_argdict, oBOOL, _opr_lt, 10, '<'), tLE: (_cmp_argdict, oBOOL, _opr_le, 10, '<='), tEQ: (_cmp_argdict, oBOOL, _opr_eq, 10, '='), tGE: (_cmp_argdict, oBOOL, _opr_ge, 10, '>='), tGT: (_cmp_argdict, oBOOL, _opr_gt, 10, '>'), tNE: (_cmp_argdict, oBOOL, _opr_ne, 10, '<>'), } unop_rules = { 0x13: (lambda x: -x, 70, '-', ''), # unary minus 0x12: (lambda x: x, 70, '+', ''), # unary plus 0x14: (lambda x: x / 100.0, 60, '', '%'),# percent } LEAF_RANK = 90 FUNC_RANK = 90 STACK_ALARM_LEVEL = 5 STACK_PANIC_LEVEL = 10 def evaluate_name_formula(bk, nobj, namex, blah=0, level=0): if level > STACK_ALARM_LEVEL: blah = 1 data = nobj.raw_formula fmlalen = nobj.basic_formula_len bv = bk.biff_version reldelta = 1 # All defined name formulas use "Method B" [OOo docs] if blah: print("::: evaluate_name_formula %r %r %d %d %r level=%d" % (namex, nobj.name, fmlalen, bv, data, level), file=bk.logfile) hex_char_dump(data, 0, fmlalen, fout=bk.logfile) if level > STACK_PANIC_LEVEL: raise XLRDError("Excessive indirect references in NAME formula") sztab = szdict[bv] pos = 0 stack = [] any_rel = 0 any_err = 0 any_external = 0 unk_opnd = Operand(oUNK, None) error_opnd = Operand(oERR, None) spush = stack.append def do_binop(opcd, stk): assert len(stk) >= 2 bop = stk.pop() aop = stk.pop() argdict, result_kind, func, rank, sym = binop_rules[opcd] otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) resop = Operand(result_kind, None, rank, otext) try: bconv = argdict[bop.kind] aconv = argdict[aop.kind] except KeyError: stk.append(resop) return if bop.value is None or aop.value is None: stk.append(resop) return bval = bconv(bop.value) aval = aconv(aop.value) result = func(aval, bval) if result_kind == oBOOL: result = 1 if result else 0 resop.value = result stk.append(resop) def do_unaryop(opcode, result_kind, stk): assert len(stk) >= 1 aop = stk.pop() val = aop.value func, rank, sym1, sym2 = unop_rules[opcode] otext = ''.join([ sym1, '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym2, ]) if val is not None: val = func(val) stk.append(Operand(result_kind, val, rank, otext)) def not_in_name_formula(op_arg, oname_arg): msg = "ERROR *** Token 0x%02x (%s) found in NAME formula" \ % (op_arg, oname_arg) raise FormulaError(msg) if fmlalen == 0: stack = [unk_opnd] while 0 <= pos < fmlalen: op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: opx = opcode + 32 else: opx = opcode oname = onames[opx] # + [" RVA"][optype] sz = sztab[opx] if blah: print("Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh" % (pos, op, oname, sz, opcode, optype), file=bk.logfile) print("Stack =", stack, file=bk.logfile) if sz == -2: msg = 'ERROR *** Unexpected token 0x%02x ("%s"); biff_version=%d' \ % (op, oname, bv) raise FormulaError(msg) if not optype: if 0x00 <= opcode <= 0x02: # unk_opnd, tExp, tTbl not_in_name_formula(op, oname) elif 0x03 <= opcode <= 0x0E: # Add, Sub, Mul, Div, Power # tConcat # tLT, ..., tNE do_binop(opcode, stack) elif opcode == 0x0F: # tIsect if blah: print("tIsect pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() sym = ' ' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF) res.text = otext if bop.kind == oERR or aop.kind == oERR: res.kind = oERR elif bop.kind == oUNK or aop.kind == oUNK: # This can happen with undefined # (go search in the current sheet) labels. # For example =Bob Sales # Each label gets a NAME record with an empty formula (!) # Evaluation of the tName token classifies it as oUNK # res.kind = oREF pass elif bop.kind == oREF == aop.kind: if aop.value is not None and bop.value is not None: assert len(aop.value) == 1 assert len(bop.value) == 1 coords = do_box_funcs( tIsectFuncs, aop.value[0], bop.value[0]) res.value = [Ref3D(coords)] elif bop.kind == oREL == aop.kind: res.kind = oREL if aop.value is not None and bop.value is not None: assert len(aop.value) == 1 assert len(bop.value) == 1 coords = do_box_funcs( tIsectFuncs, aop.value[0], bop.value[0]) relfa = aop.value[0].relflags relfb = bop.value[0].relflags if relfa == relfb: res.value = [Ref3D(coords + relfa)] else: pass spush(res) if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x10: # tList if blah: print("tList pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() sym = ',' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: res.kind = oERR elif bop.kind in (oREF, oREL) and aop.kind in (oREF, oREL): res.kind = oREF if aop.kind == oREL or bop.kind == oREL: res.kind = oREL if aop.value is not None and bop.value is not None: assert len(aop.value) >= 1 assert len(bop.value) == 1 res.value = aop.value + bop.value else: pass spush(res) if blah: print("tList post", stack, file=bk.logfile) elif opcode == 0x11: # tRange if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() sym = ':' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: res = oERR elif bop.kind == oREF == aop.kind: if aop.value is not None and bop.value is not None: assert len(aop.value) == 1 assert len(bop.value) == 1 coords = do_box_funcs( tRangeFuncs, aop.value[0], bop.value[0]) res.value = [Ref3D(coords)] elif bop.kind == oREL == aop.kind: res.kind = oREL if aop.value is not None and bop.value is not None: assert len(aop.value) == 1 assert len(bop.value) == 1 coords = do_box_funcs( tRangeFuncs, aop.value[0], bop.value[0]) relfa = aop.value[0].relflags relfb = bop.value[0].relflags if relfa == relfb: res.value = [Ref3D(coords + relfa)] else: pass spush(res) if blah: print("tRange post", stack, file=bk.logfile) elif 0x12 <= opcode <= 0x14: # tUplus, tUminus, tPercent do_unaryop(opcode, oNUM, stack) elif opcode == 0x15: # tParen # source cosmetics pass elif opcode == 0x16: # tMissArg spush(Operand(oMSNG, None, LEAF_RANK, '')) elif opcode == 0x17: # tStr if bv <= 70: strg, newpos = unpack_string_update_pos( data, pos+1, bk.encoding, lenlen=1) else: strg, newpos = unpack_unicode_update_pos( data, pos+1, lenlen=1) sz = newpos - pos if blah: print(" sz=%d strg=%r" % (sz, strg), file=bk.logfile) text = '"' + strg.replace('"', '""') + '"' spush(Operand(oSTRG, strg, LEAF_RANK, text)) elif opcode == 0x18: # tExtended # new with BIFF 8 assert bv >= 80 # not in OOo docs raise FormulaError("tExtended token not implemented") elif opcode == 0x19: # tAttr subop, nc = unpack("= 1 aop = stack[-1] otext = 'SUM(%s)' % aop.text stack[-1] = Operand(oNUM, None, FUNC_RANK, otext) else: sz = 4 if blah: print(" subop=%02xh subname=t%s sz=%d nc=%02xh" % (subop, subname, sz, nc), file=bk.logfile) elif 0x1A <= opcode <= 0x1B: # tSheet, tEndSheet assert bv < 50 raise FormulaError("tSheet & tEndsheet tokens not implemented") elif 0x1C <= opcode <= 0x1F: # tErr, tBool, tInt, tNum inx = opcode - 0x1C nb = [1, 1, 2, 8][inx] kind = [oERR, oBOOL, oNUM, oNUM][inx] value, = unpack("<" + "BBHd"[inx], data[pos+1:pos+1+nb]) if inx == 2: # tInt value = float(value) text = str(value) elif inx == 3: # tNum text = str(value) elif inx == 1: # tBool text = ('FALSE', 'TRUE')[value] else: text = '"' +error_text_from_code[value] + '"' spush(Operand(kind, value, LEAF_RANK, text)) else: raise FormulaError("Unhandled opcode: 0x%02x" % opcode) if sz <= 0: raise FormulaError("Size not set for opcode 0x%02x" % opcode) pos += sz continue if opcode == 0x00: # tArray spush(unk_opnd) elif opcode == 0x01: # tFunc nb = 1 + int(bv >= 40) funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0] func_attrs = func_defs.get(funcx, None) if not func_attrs: print("*** formula/tFunc unknown FuncID:%d" % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, nargs = func_attrs[:2] if blah: print(" FuncID=%d name=%s nargs=%d" % (funcx, func_name, nargs), file=bk.logfile) assert len(stack) >= nargs if nargs: argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) del stack[-nargs:] else: otext = func_name + "()" res = Operand(oUNK, None, FUNC_RANK, otext) spush(res) elif opcode == 0x02: #tFuncVar nb = 1 + int(bv >= 40) nargs, funcx = unpack("= nargs assert len(stack) >= nargs argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) res = Operand(oUNK, None, FUNC_RANK, otext) if funcx == 1: # IF testarg = stack[-nargs] if testarg.kind not in (oNUM, oBOOL): if blah and testarg.kind != oUNK: print("IF testarg kind?", file=bk.logfile) elif testarg.value not in (0, 1): if blah and testarg.value is not None: print("IF testarg value?", file=bk.logfile) else: if nargs == 2 and not testarg.value: # IF(FALSE, tv) => FALSE res.kind, res.value = oBOOL, 0 else: respos = -nargs + 2 - int(testarg.value) chosen = stack[respos] if chosen.kind == oMSNG: res.kind, res.value = oNUM, 0 else: res.kind, res.value = chosen.kind, chosen.value if blah: print("$$$$$$ IF => constant", file=bk.logfile) elif funcx == 100: # CHOOSE testarg = stack[-nargs] if testarg.kind == oNUM: if 1 <= testarg.value < nargs: chosen = stack[-nargs + int(testarg.value)] if chosen.kind == oMSNG: res.kind, res.value = oNUM, 0 else: res.kind, res.value = chosen.kind, chosen.value del stack[-nargs:] spush(res) elif opcode == 0x03: #tName tgtnamex = unpack("> bk.logfile, " ", res # spush(res) elif opcode == 0x0D: #tAreaN not_in_name_formula(op, oname) # res = get_cell_range_addr(data, pos+1, bv, reldelta=1) # # note *ALL* tAreaN usage has signed offset for relative addresses # any_rel = 1 # if blah: print >> bk.logfile, " ", res elif opcode == 0x1A: # tRef3d if bv >= 80: res = get_cell_addr(data, pos+3, bv, reldelta) refx = unpack("= 80: res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta) refx = unpack("= 80: refx, tgtnamex = unpack(" 0: refx -= 1 elif refx < 0: refx = -refx - 1 else: dodgy = 1 if blah: print(" origrefx=%d refx=%d tgtnamex=%d dodgy=%d" % (origrefx, refx, tgtnamex, dodgy), file=bk.logfile) if tgtnamex == namex: if blah: print("!!!! Self-referential !!!!", file=bk.logfile) dodgy = any_err = 1 if not dodgy: if bv >= 80: shx1, shx2 = get_externsheet_local_range(bk, refx, blah) elif origrefx > 0: shx1, shx2 = (-4, -4) # external ref else: exty = bk._externsheet_type_b57[refx] if exty == 4: # non-specific sheet in own doc't shx1, shx2 = (-1, -1) # internal, any sheet else: shx1, shx2 = (-666, -666) if dodgy or shx1 < -1: otext = "<>" \ % (tgtnamex, origrefx) res = Operand(oUNK, None, LEAF_RANK, otext) else: tgtobj = bk.name_obj_list[tgtnamex] if not tgtobj.evaluated: ### recursive ### evaluate_name_formula(bk, tgtobj, tgtnamex, blah, level+1) if tgtobj.macro or tgtobj.binary or tgtobj.any_err: if blah: tgtobj.dump( bk.logfile, header="!!! bad tgtobj !!!", footer="------------------", ) res = Operand(oUNK, None) any_err = any_err or tgtobj.macro or tgtobj.binary or tgtobj.any_err any_rel = any_rel or tgtobj.any_rel else: assert len(tgtobj.stack) == 1 res = copy.deepcopy(tgtobj.stack[0]) res.rank = LEAF_RANK if tgtobj.scope == -1: res.text = tgtobj.name else: res.text = "%s!%s" \ % (bk._sheet_names[tgtobj.scope], tgtobj.name) if blah: print(" tNameX: setting text to", repr(res.text), file=bk.logfile) spush(res) elif opcode in error_opcodes: any_err = 1 spush(error_opnd) else: if blah: print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile) any_err = 1 if sz <= 0: raise FormulaError("Fatal: token size is not positive") pos += sz any_rel = not not any_rel if blah: fprintf(bk.logfile, "End of formula. level=%d any_rel=%d any_err=%d stack=%r\n", level, not not any_rel, any_err, stack) if len(stack) >= 2: print("*** Stack has unprocessed args", file=bk.logfile) print(file=bk.logfile) nobj.stack = stack if len(stack) != 1: nobj.result = None else: nobj.result = stack[0] nobj.any_rel = any_rel nobj.any_err = any_err nobj.any_external = any_external nobj.evaluated = 1 #### under construction ############################################################################# def decompile_formula(bk, fmla, fmlalen, fmlatype=None, browx=None, bcolx=None, blah=0, level=0, r1c1=0): if level > STACK_ALARM_LEVEL: blah = 1 reldelta = fmlatype in (FMLA_TYPE_SHARED, FMLA_TYPE_NAME, FMLA_TYPE_COND_FMT, FMLA_TYPE_DATA_VAL) data = fmla bv = bk.biff_version if blah: print("::: decompile_formula len=%d fmlatype=%r browx=%r bcolx=%r reldelta=%d %r level=%d" % (fmlalen, fmlatype, browx, bcolx, reldelta, data, level), file=bk.logfile) hex_char_dump(data, 0, fmlalen, fout=bk.logfile) if level > STACK_PANIC_LEVEL: raise XLRDError("Excessive indirect references in formula") sztab = szdict[bv] pos = 0 stack = [] any_rel = 0 any_err = 0 unk_opnd = Operand(oUNK, None) error_opnd = Operand(oERR, None) spush = stack.append def do_binop(opcd, stk): assert len(stk) >= 2 bop = stk.pop() aop = stk.pop() argdict, result_kind, func, rank, sym = binop_rules[opcd] otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) resop = Operand(result_kind, None, rank, otext) stk.append(resop) def do_unaryop(opcode, result_kind, stk): assert len(stk) >= 1 aop = stk.pop() func, rank, sym1, sym2 = unop_rules[opcode] otext = ''.join([ sym1, '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym2, ]) stk.append(Operand(result_kind, None, rank, otext)) def unexpected_opcode(op_arg, oname_arg): msg = "ERROR *** Unexpected token 0x%02x (%s) found in formula type %s" \ % (op_arg, oname_arg, FMLA_TYPEDESCR_MAP[fmlatype]) print(msg, file=bk.logfile) # raise FormulaError(msg) if fmlalen == 0: stack = [unk_opnd] while 0 <= pos < fmlalen: op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: opx = opcode + 32 else: opx = opcode oname = onames[opx] # + [" RVA"][optype] sz = sztab[opx] if blah: print("Pos:%d Op:0x%02x opname:t%s Sz:%d opcode:%02xh optype:%02xh" % (pos, op, oname, sz, opcode, optype), file=bk.logfile) print("Stack =", stack, file=bk.logfile) if sz == -2: msg = 'ERROR *** Unexpected token 0x%02x ("%s"); biff_version=%d' \ % (op, oname, bv) raise FormulaError(msg) if _TOKEN_NOT_ALLOWED(opx, 0) & fmlatype: unexpected_opcode(op, oname) if not optype: if opcode <= 0x01: # tExp if bv >= 30: fmt = '= 2 bop = stack.pop() aop = stack.pop() sym = ' ' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF) res.text = otext if bop.kind == oERR or aop.kind == oERR: res.kind = oERR elif bop.kind == oUNK or aop.kind == oUNK: # This can happen with undefined # (go search in the current sheet) labels. # For example =Bob Sales # Each label gets a NAME record with an empty formula (!) # Evaluation of the tName token classifies it as oUNK # res.kind = oREF pass elif bop.kind == oREF == aop.kind: pass elif bop.kind == oREL == aop.kind: res.kind = oREL else: pass spush(res) if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x10: # tList if blah: print("tList pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() sym = ',' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: res.kind = oERR elif bop.kind in (oREF, oREL) and aop.kind in (oREF, oREL): res.kind = oREF if aop.kind == oREL or bop.kind == oREL: res.kind = oREL else: pass spush(res) if blah: print("tList post", stack, file=bk.logfile) elif opcode == 0x11: # tRange if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() sym = ':' rank = 80 ########## check ####### otext = ''.join([ '('[:aop.rank < rank], aop.text, ')'[:aop.rank < rank], sym, '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: res = oERR elif bop.kind == oREF == aop.kind: pass else: pass spush(res) if blah: print("tRange post", stack, file=bk.logfile) elif 0x12 <= opcode <= 0x14: # tUplus, tUminus, tPercent do_unaryop(opcode, oNUM, stack) elif opcode == 0x15: # tParen # source cosmetics pass elif opcode == 0x16: # tMissArg spush(Operand(oMSNG, None, LEAF_RANK, '')) elif opcode == 0x17: # tStr if bv <= 70: strg, newpos = unpack_string_update_pos( data, pos+1, bk.encoding, lenlen=1) else: strg, newpos = unpack_unicode_update_pos( data, pos+1, lenlen=1) sz = newpos - pos if blah: print(" sz=%d strg=%r" % (sz, strg), file=bk.logfile) text = '"' + strg.replace('"', '""') + '"' spush(Operand(oSTRG, None, LEAF_RANK, text)) elif opcode == 0x18: # tExtended # new with BIFF 8 assert bv >= 80 # not in OOo docs, don't even know how to determine its length raise FormulaError("tExtended token not implemented") elif opcode == 0x19: # tAttr subop, nc = unpack("= 1 aop = stack[-1] otext = 'SUM(%s)' % aop.text stack[-1] = Operand(oNUM, None, FUNC_RANK, otext) else: sz = 4 if blah: print(" subop=%02xh subname=t%s sz=%d nc=%02xh" % (subop, subname, sz, nc), file=bk.logfile) elif 0x1A <= opcode <= 0x1B: # tSheet, tEndSheet assert bv < 50 raise FormulaError("tSheet & tEndsheet tokens not implemented") elif 0x1C <= opcode <= 0x1F: # tErr, tBool, tInt, tNum inx = opcode - 0x1C nb = [1, 1, 2, 8][inx] kind = [oERR, oBOOL, oNUM, oNUM][inx] value, = unpack("<" + "BBHd"[inx], data[pos+1:pos+1+nb]) if inx == 2: # tInt value = float(value) text = str(value) elif inx == 3: # tNum text = str(value) elif inx == 1: # tBool text = ('FALSE', 'TRUE')[value] else: text = '"' +error_text_from_code[value] + '"' spush(Operand(kind, None, LEAF_RANK, text)) else: raise FormulaError("Unhandled opcode: 0x%02x" % opcode) if sz <= 0: raise FormulaError("Size not set for opcode 0x%02x" % opcode) pos += sz continue if opcode == 0x00: # tArray spush(unk_opnd) elif opcode == 0x01: # tFunc nb = 1 + int(bv >= 40) funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0] func_attrs = func_defs.get(funcx, None) if not func_attrs: print("*** formula/tFunc unknown FuncID:%d" % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, nargs = func_attrs[:2] if blah: print(" FuncID=%d name=%s nargs=%d" % (funcx, func_name, nargs), file=bk.logfile) assert len(stack) >= nargs if nargs: argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) del stack[-nargs:] else: otext = func_name + "()" res = Operand(oUNK, None, FUNC_RANK, otext) spush(res) elif opcode == 0x02: #tFuncVar nb = 1 + int(bv >= 40) nargs, funcx = unpack("= nargs assert len(stack) >= nargs argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) res = Operand(oUNK, None, FUNC_RANK, otext) del stack[-nargs:] spush(res) elif opcode == 0x03: #tName tgtnamex = unpack("> bk.logfile, " ", res res1, res2 = get_cell_range_addr( data, pos+1, bv, reldelta, browx, bcolx) if blah: print(" ", res1, res2, file=bk.logfile) rowx1, colx1, row_rel1, col_rel1 = res1 rowx2, colx2, row_rel2, col_rel2 = res2 coords = (rowx1, rowx2+1, colx1, colx2+1) relflags = (row_rel1, row_rel2, col_rel1, col_rel2) if sum(relflags): # relative okind = oREL else: okind = oREF if blah: print(" ", coords, relflags, file=bk.logfile) otext = rangename2drel(coords, relflags, browx, bcolx, r1c1) res = Operand(okind, None, LEAF_RANK, otext) spush(res) elif opcode == 0x1A: # tRef3d if bv >= 80: res = get_cell_addr(data, pos+3, bv, reldelta, browx, bcolx) refx = unpack("= 80: res1, res2 = get_cell_range_addr(data, pos+3, bv, reldelta) refx = unpack("= 80: refx, tgtnamex = unpack(" 0: refx -= 1 elif refx < 0: refx = -refx - 1 else: dodgy = 1 if blah: print(" origrefx=%d refx=%d tgtnamex=%d dodgy=%d" % (origrefx, refx, tgtnamex, dodgy), file=bk.logfile) # if tgtnamex == namex: # if blah: print >> bk.logfile, "!!!! Self-referential !!!!" # dodgy = any_err = 1 if not dodgy: if bv >= 80: shx1, shx2 = get_externsheet_local_range(bk, refx, blah) elif origrefx > 0: shx1, shx2 = (-4, -4) # external ref else: exty = bk._externsheet_type_b57[refx] if exty == 4: # non-specific sheet in own doc't shx1, shx2 = (-1, -1) # internal, any sheet else: shx1, shx2 = (-666, -666) okind = oUNK ovalue = None if shx1 == -5: # addin func name okind = oSTRG ovalue = bk.addin_func_names[tgtnamex] otext = '"' + ovalue.replace('"', '""') + '"' elif dodgy or shx1 < -1: otext = "<>" \ % (tgtnamex, origrefx) else: tgtobj = bk.name_obj_list[tgtnamex] if tgtobj.scope == -1: otext = tgtobj.name else: otext = "%s!%s" \ % (bk._sheet_names[tgtobj.scope], tgtobj.name) if blah: print(" tNameX: setting text to", repr(res.text), file=bk.logfile) res = Operand(okind, ovalue, LEAF_RANK, otext) spush(res) elif opcode in error_opcodes: any_err = 1 spush(error_opnd) else: if blah: print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile) any_err = 1 if sz <= 0: raise FormulaError("Fatal: token size is not positive") pos += sz any_rel = not not any_rel if blah: print("End of formula. level=%d any_rel=%d any_err=%d stack=%r" % (level, not not any_rel, any_err, stack), file=bk.logfile) if len(stack) >= 2: print("*** Stack has unprocessed args", file=bk.logfile) print(file=bk.logfile) if len(stack) != 1: result = None else: result = stack[0].text return result #### under deconstruction ### def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): if blah: print("dump_formula", fmlalen, bv, len(data), file=bk.logfile) hex_char_dump(data, 0, fmlalen, fout=bk.logfile) assert bv >= 80 #### this function needs updating #### sztab = szdict[bv] pos = 0 stack = [] any_rel = 0 any_err = 0 spush = stack.append while 0 <= pos < fmlalen: op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: opx = opcode + 32 else: opx = opcode oname = onames[opx] # + [" RVA"][optype] sz = sztab[opx] if blah: print("Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh" % (pos, op, oname, sz, opcode, optype), file=bk.logfile) if not optype: if 0x01 <= opcode <= 0x02: # tExp, tTbl # reference to a shared formula or table record rowx, colx = unpack("= 2 bop = stack.pop() aop = stack.pop() spush(aop + bop) if blah: print("tlist post", stack, file=bk.logfile) elif opcode == 0x11: # tRange if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() assert len(aop) == 1 assert len(bop) == 1 result = do_box_funcs(tRangeFuncs, aop[0], bop[0]) spush(result) if blah: print("tRange post", stack, file=bk.logfile) elif opcode == 0x0F: # tIsect if blah: print("tIsect pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() assert len(aop) == 1 assert len(bop) == 1 result = do_box_funcs(tIsectFuncs, aop[0], bop[0]) spush(result) if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x19: # tAttr subop, nc = unpack("= 40) funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb]) if blah: print(" FuncID=%d" % funcx, file=bk.logfile) elif opcode == 0x02: #tFuncVar nb = 1 + int(bv >= 40) nargs, funcx = unpack("= 2: print("*** Stack has unprocessed args", file=bk.logfile) # === Some helper functions for displaying cell references === # I'm aware of only one possibility of a sheet-relative component in # a reference: a 2D reference located in the "current sheet". # xlrd stores this internally with bounds of (0, 1, ...) and # relative flags of (1, 1, ...). These functions display the # sheet component as empty, just like Excel etc. def rownamerel(rowx, rowxrel, browx=None, r1c1=0): # if no base rowx is provided, we have to return r1c1 if browx is None: r1c1 = True if not rowxrel: if r1c1: return "R%d" % (rowx+1) return "$%d" % (rowx+1) if r1c1: if rowx: return "R[%d]" % rowx return "R" return "%d" % ((browx + rowx) % 65536 + 1) def colnamerel(colx, colxrel, bcolx=None, r1c1=0): # if no base colx is provided, we have to return r1c1 if bcolx is None: r1c1 = True if not colxrel: if r1c1: return "C%d" % (colx + 1) return "$" + colname(colx) if r1c1: if colx: return "C[%d]" % colx return "C" return colname((bcolx + colx) % 256) def cellname(rowx, colx): """Utility function: ``(5, 7)`` => ``'H6'``""" return "%s%d" % (colname(colx), rowx+1) def cellnameabs(rowx, colx, r1c1=0): """Utility function: ``(5, 7)`` => ``'$H$6'``""" if r1c1: return "R%dC%d" % (rowx+1, colx+1) return "$%s$%d" % (colname(colx), rowx+1) def cellnamerel(rowx, colx, rowxrel, colxrel, browx=None, bcolx=None, r1c1=0): if not rowxrel and not colxrel: return cellnameabs(rowx, colx, r1c1) if (rowxrel and browx is None) or (colxrel and bcolx is None): # must flip the whole cell into R1C1 mode r1c1 = True c = colnamerel(colx, colxrel, bcolx, r1c1) r = rownamerel(rowx, rowxrel, browx, r1c1) if r1c1: return r + c return c + r def colname(colx): """Utility function: ``7`` => ``'H'``, ``27`` => ``'AB'``""" alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if colx <= 25: return alphabet[colx] else: xdiv26, xmod26 = divmod(colx, 26) return alphabet[xdiv26 - 1] + alphabet[xmod26] def rangename2d(rlo, rhi, clo, chi, r1c1=0): """ ``(5, 20, 7, 10)`` => ``'$H$6:$J$20'`` """ if r1c1: return if rhi == rlo+1 and chi == clo+1: return cellnameabs(rlo, clo, r1c1) return "%s:%s" % (cellnameabs(rlo, clo, r1c1), cellnameabs(rhi-1, chi-1, r1c1)) def rangename2drel(rlo_rhi_clo_chi, rlorel_rhirel_clorel_chirel, browx=None, bcolx=None, r1c1=0): rlo, rhi, clo, chi = rlo_rhi_clo_chi rlorel, rhirel, clorel, chirel = rlorel_rhirel_clorel_chirel if (rlorel or rhirel) and browx is None: r1c1 = True if (clorel or chirel) and bcolx is None: r1c1 = True return "%s:%s" % ( cellnamerel(rlo, clo, rlorel, clorel, browx, bcolx, r1c1), cellnamerel(rhi-1, chi-1, rhirel, chirel, browx, bcolx, r1c1), ) def rangename3d(book, ref3d): """ Utility function: ``Ref3D(1, 4, 5, 20, 7, 10)`` => ``'Sheet2:Sheet3!$H$6:$J$20'`` (assuming Excel's default sheetnames) """ coords = ref3d.coords return "%s!%s" % ( sheetrange(book, *coords[:2]), rangename2d(*coords[2:6])) def rangename3drel(book, ref3d, browx=None, bcolx=None, r1c1=0): """ Utility function: ``Ref3D(coords=(0, 1, -32, -22, -13, 13), relflags=(0, 0, 1, 1, 1, 1))`` In R1C1 mode => ``'Sheet1!R[-32]C[-13]:R[-23]C[12]'`` In A1 mode => depends on base cell ``(browx, bcolx)`` """ coords = ref3d.coords relflags = ref3d.relflags shdesc = sheetrangerel(book, coords[:2], relflags[:2]) rngdesc = rangename2drel(coords[2:6], relflags[2:6], browx, bcolx, r1c1) if not shdesc: return rngdesc return "%s!%s" % (shdesc, rngdesc) def quotedsheetname(shnames, shx): if shx >= 0: shname = shnames[shx] else: shname = { -1: "?internal; any sheet?", -2: "internal; deleted sheet", -3: "internal; macro sheet", -4: "<>", }.get(shx, "?error %d?" % shx) if "'" in shname: return "'" + shname.replace("'", "''") + "'" if " " in shname: return "'" + shname + "'" return shname def sheetrange(book, slo, shi): shnames = book.sheet_names() shdesc = quotedsheetname(shnames, slo) if slo != shi-1: shdesc += ":" + quotedsheetname(shnames, shi-1) return shdesc def sheetrangerel(book, srange, srangerel): slo, shi = srange slorel, shirel = srangerel if not slorel and not shirel: return sheetrange(book, slo, shi) assert (slo == 0 == shi-1) and slorel and shirel return "" # ============================================================== xlrd-2.0.1/xlrd/info.py000066400000000000000000000000441376464300000147420ustar00rootroot00000000000000__version__ = __VERSION__ = "2.0.1" xlrd-2.0.1/xlrd/sheet.py000066400000000000000000003204661376464300000151340ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2005-2013 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. from __future__ import print_function from array import array from struct import calcsize, unpack from .biffh import * from .formatting import Format, nearest_colour_index from .formula import ( FMLA_TYPE_CELL, FMLA_TYPE_SHARED, decompile_formula, dump_formula, rangename2d, ) from .timemachine import * DEBUG = 0 OBJ_MSO_DEBUG = 0 _WINDOW2_options = ( # Attribute names and initial values to use in case # a WINDOW2 record is not written. ("show_formulas", 0), ("show_grid_lines", 1), ("show_sheet_headers", 1), ("panes_are_frozen", 0), ("show_zero_values", 1), ("automatic_grid_line_colour", 1), ("columns_from_right_to_left", 0), ("show_outline_symbols", 1), ("remove_splits_if_pane_freeze_is_removed", 0), # Multiple sheets can be selected, but only one can be active # (hold down Ctrl and click multiple tabs in the file in OOo) ("sheet_selected", 0), # "sheet_visible" should really be called "sheet_active" # and is 1 when this sheet is the sheet displayed when the file # is open. More than likely only one sheet should ever be set as # visible. # This would correspond to the Book's sheet_active attribute, but # that doesn't exist as WINDOW1 records aren't currently processed. # The real thing is the visibility attribute from the BOUNDSHEET record. ("sheet_visible", 0), ("show_in_page_break_preview", 0), ) class Sheet(BaseObject): """ Contains the data for one worksheet. In the cell access functions, ``rowx`` is a row index, counting from zero, and ``colx`` is a column index, counting from zero. Negative values for row/column indexes and slice positions are supported in the expected fashion. For information about cell types and cell values, refer to the documentation of the :class:`Cell` class. .. warning:: You don't instantiate this class yourself. You access :class:`Sheet` objects via the :class:`~xlrd.book.Book` object that was returned when you called :func:`xlrd.open_workbook`. """ #: Name of sheet. name = '' #: A reference to the :class:`~xlrd.book.Book` object to which this sheet #: belongs. #: #: Example usage: ``some_sheet.book.datemode`` book = None #: Number of rows in sheet. A row index is in ``range(thesheet.nrows)``. nrows = 0 #: Nominal number of columns in sheet. It is one more than the maximum #: column index found, ignoring trailing empty cells. #: See also the ``ragged_rows`` parameter to :func:`~xlrd.open_workbook` #: and :meth:`~xlrd.sheet.Sheet.row_len`. ncols = 0 #: The map from a column index to a :class:`Colinfo` object. Often there is #: an entry in ``COLINFO`` records for all column indexes in ``range(257)``. #: #: .. note:: #: xlrd ignores the entry for the non-existent #: 257th column. #: #: On the other hand, there may be no entry for unused columns. #: #: .. versionadded:: 0.6.1 #: #: Populated only if ``open_workbook(..., formatting_info=True)`` colinfo_map = {} #: The map from a row index to a :class:`Rowinfo` object. #: #: ..note:: #: It is possible to have missing entries -- at least one source of #: XLS files doesn't bother writing ``ROW`` records. #: #: .. versionadded:: 0.6.1 #: #: Populated only if ``open_workbook(..., formatting_info=True)`` rowinfo_map = {} #: List of address ranges of cells containing column labels. #: These are set up in Excel by Insert > Name > Labels > Columns. #: #: .. versionadded:: 0.6.0 #: #: How to deconstruct the list: #: #: .. code-block:: python #: #: for crange in thesheet.col_label_ranges: #: rlo, rhi, clo, chi = crange #: for rx in xrange(rlo, rhi): #: for cx in xrange(clo, chi): #: print "Column label at (rowx=%d, colx=%d) is %r" \ #: (rx, cx, thesheet.cell_value(rx, cx)) col_label_ranges = [] #: List of address ranges of cells containing row labels. #: For more details, see :attr:`col_label_ranges`. #: #: .. versionadded:: 0.6.0 row_label_ranges = [] #: List of address ranges of cells which have been merged. #: These are set up in Excel by Format > Cells > Alignment, then ticking #: the "Merge cells" box. #: #: .. note:: #: The upper limits are exclusive: i.e. ``[2, 3, 7, 9]`` only #: spans two cells. #: #: .. note:: Extracted only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.6.1 #: #: How to deconstruct the list: #: #: .. code-block:: python #: #: for crange in thesheet.merged_cells: #: rlo, rhi, clo, chi = crange #: for rowx in xrange(rlo, rhi): #: for colx in xrange(clo, chi): #: # cell (rlo, clo) (the top left one) will carry the data #: # and formatting info; the remainder will be recorded as #: # blank cells, but a renderer will apply the formatting info #: # for the top left cell (e.g. border, pattern) to all cells in #: # the range. merged_cells = [] #: Mapping of ``(rowx, colx)`` to list of ``(offset, font_index)`` tuples. #: The offset defines where in the string the font begins to be used. #: Offsets are expected to be in ascending order. #: If the first offset is not zero, the meaning is that the cell's ``XF``'s #: font should be used from offset 0. #: #: This is a sparse mapping. There is no entry for cells that are not #: formatted with rich text. #: #: How to use: #: #: .. code-block:: python #: #: runlist = thesheet.rich_text_runlist_map.get((rowx, colx)) #: if runlist: #: for offset, font_index in runlist: #: # do work here. #: pass #: #: .. versionadded:: 0.7.2 #: #: Populated only if ``open_workbook(..., formatting_info=True)`` rich_text_runlist_map = {} #: Default column width from ``DEFCOLWIDTH`` record, else ``None``. #: From the OOo docs: #: #: Column width in characters, using the width of the zero character #: from default font (first FONT record in the file). Excel adds some #: extra space to the default width, depending on the default font and #: default font size. The algorithm how to exactly calculate the resulting #: column width is not known. #: Example: The default width of 8 set in this record results in a column #: width of 8.43 using Arial font with a size of 10 points. #: #: For the default hierarchy, refer to the :class:`Colinfo` class. #: #: .. versionadded:: 0.6.1 defcolwidth = None #: Default column width from ``STANDARDWIDTH`` record, else ``None``. #: #: From the OOo docs: #: #: Default width of the columns in 1/256 of the width of the zero #: character, using default font (first FONT record in the file). #: #: For the default hierarchy, refer to the :class:`Colinfo` class. #: #: .. versionadded:: 0.6.1 standardwidth = None #: Default value to be used for a row if there is #: no ``ROW`` record for that row. #: From the *optional* ``DEFAULTROWHEIGHT`` record. default_row_height = None #: Default value to be used for a row if there is #: no ``ROW`` record for that row. #: From the *optional* ``DEFAULTROWHEIGHT`` record. default_row_height_mismatch = None #: Default value to be used for a row if there is #: no ``ROW`` record for that row. #: From the *optional* ``DEFAULTROWHEIGHT`` record. default_row_hidden = None #: Default value to be used for a row if there is #: no ``ROW`` record for that row. #: From the *optional* ``DEFAULTROWHEIGHT`` record. default_additional_space_above = None #: Default value to be used for a row if there is #: no ``ROW`` record for that row. #: From the *optional* ``DEFAULTROWHEIGHT`` record. default_additional_space_below = None #: Visibility of the sheet: #: :: #: #: 0 = visible #: 1 = hidden (can be unhidden by user -- Format -> Sheet -> Unhide) #: 2 = "very hidden" (can be unhidden only by VBA macro). visibility = 0 #: A 256-element tuple corresponding to the contents of the GCW record for #: this sheet. If no such record, treat as all bits zero. #: Applies to BIFF4-7 only. See docs of the :class:`Colinfo` class for #: discussion. gcw = (0, ) * 256 #: A list of :class:`Hyperlink` objects corresponding to ``HLINK`` records #: found in the worksheet. #: #: .. versionadded:: 0.7.2 hyperlink_list = [] #: A sparse mapping from ``(rowx, colx)`` to an item in #: :attr:`~xlrd.sheet.Sheet.hyperlink_list`. #: Cells not covered by a hyperlink are not mapped. #: It is possible using the Excel UI to set up a hyperlink that #: covers a larger-than-1x1 rectangle of cells. #: Hyperlink rectangles may overlap (Excel doesn't check). #: When a multiply-covered cell is clicked on, the hyperlink that is #: activated #: (and the one that is mapped here) is the last in #: :attr:`~xlrd.sheet.Sheet.hyperlink_list`. #: #: .. versionadded:: 0.7.2 hyperlink_map = {} #: A sparse mapping from ``(rowx, colx)`` to a :class:`Note` object. #: Cells not containing a note ("comment") are not mapped. #: #: .. versionadded:: 0.7.2 cell_note_map = {} #: Number of columns in left pane (frozen panes; for split panes, see #: comments in code) vert_split_pos = 0 #: Number of rows in top pane (frozen panes; for split panes, see comments #: in code) horz_split_pos = 0 #: Index of first visible row in bottom frozen/split pane horz_split_first_visible = 0 #: Index of first visible column in right frozen/split pane vert_split_first_visible = 0 #: Frozen panes: ignore it. Split panes: explanation and diagrams in #: OOo docs. split_active_pane = 0 #: Boolean specifying if a ``PANE`` record was present, ignore unless you're #: ``xlutils.copy`` has_pane_record = 0 #: A list of the horizontal page breaks in this sheet. #: Breaks are tuples in the form #: ``(index of row after break, start col index, end col index)``. #: #: Populated only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.7.2 horizontal_page_breaks = [] #: A list of the vertical page breaks in this sheet. #: Breaks are tuples in the form #: ``(index of col after break, start row index, end row index)``. #: #: Populated only if ``open_workbook(..., formatting_info=True)`` #: #: .. versionadded:: 0.7.2 vertical_page_breaks = [] def __init__(self, book, position, name, number): self.book = book self.biff_version = book.biff_version self._position = position self.logfile = book.logfile self.bt = array('B', [XL_CELL_EMPTY]) self.bf = array('h', [-1]) self.name = name self.number = number self.verbosity = book.verbosity self.formatting_info = book.formatting_info self.ragged_rows = book.ragged_rows if self.ragged_rows: self.put_cell = self.put_cell_ragged else: self.put_cell = self.put_cell_unragged self._xf_index_to_xl_type_map = book._xf_index_to_xl_type_map self.nrows = 0 # actual, including possibly empty cells self.ncols = 0 self._maxdatarowx = -1 # highest rowx containing a non-empty cell self._maxdatacolx = -1 # highest colx containing a non-empty cell self._dimnrows = 0 # as per DIMENSIONS record self._dimncols = 0 self._cell_values = [] self._cell_types = [] self._cell_xf_indexes = [] self.defcolwidth = None self.standardwidth = None self.default_row_height = None self.default_row_height_mismatch = 0 self.default_row_hidden = 0 self.default_additional_space_above = 0 self.default_additional_space_below = 0 self.colinfo_map = {} self.rowinfo_map = {} self.col_label_ranges = [] self.row_label_ranges = [] self.merged_cells = [] self.rich_text_runlist_map = {} self.horizontal_page_breaks = [] self.vertical_page_breaks = [] self._xf_index_stats = [0, 0, 0, 0] self.visibility = book._sheet_visibility[number] # from BOUNDSHEET record for attr, defval in _WINDOW2_options: setattr(self, attr, defval) self.first_visible_rowx = 0 self.first_visible_colx = 0 self.gridline_colour_index = 0x40 self.gridline_colour_rgb = None # pre-BIFF8 self.hyperlink_list = [] self.hyperlink_map = {} self.cell_note_map = {} # Values calculated by xlrd to predict the mag factors that # will actually be used by Excel to display your worksheet. # Pass these values to xlwt when writing XLS files. # Warning 1: Behaviour of OOo Calc and Gnumeric has been observed to differ from Excel's. # Warning 2: A value of zero means almost exactly what it says. Your sheet will be # displayed as a very tiny speck on the screen. xlwt will reject attempts to set # a mag_factor that is not (10 <= mag_factor <= 400). self.cooked_page_break_preview_mag_factor = 60 self.cooked_normal_view_mag_factor = 100 # Values (if any) actually stored on the XLS file self.cached_page_break_preview_mag_factor = 0 # default (60%), from WINDOW2 record self.cached_normal_view_mag_factor = 0 # default (100%), from WINDOW2 record self.scl_mag_factor = None # from SCL record self._ixfe = None # BIFF2 only self._cell_attr_to_xfx = {} # BIFF2.0 only if self.biff_version >= 80: self.utter_max_rows = 65536 else: self.utter_max_rows = 16384 self.utter_max_cols = 256 self._first_full_rowx = -1 # self._put_cell_exceptions = 0 # self._put_cell_row_widenings = 0 # self._put_cell_rows_appended = 0 # self._put_cell_cells_appended = 0 def cell(self, rowx, colx): """ :class:`Cell` object in the given row and column. """ if self.formatting_info: xfx = self.cell_xf_index(rowx, colx) else: xfx = None return Cell( self._cell_types[rowx][colx], self._cell_values[rowx][colx], xfx, ) def cell_value(self, rowx, colx): "Value of the cell in the given row and column." return self._cell_values[rowx][colx] def cell_type(self, rowx, colx): """ Type of the cell in the given row and column. Refer to the documentation of the :class:`Cell` class. """ return self._cell_types[rowx][colx] def cell_xf_index(self, rowx, colx): """ XF index of the cell in the given row and column. This is an index into :attr:`~xlrd.book.Book.xf_list`. .. versionadded:: 0.6.1 """ self.req_fmt_info() xfx = self._cell_xf_indexes[rowx][colx] if xfx > -1: self._xf_index_stats[0] += 1 return xfx # Check for a row xf_index try: xfx = self.rowinfo_map[rowx].xf_index if xfx > -1: self._xf_index_stats[1] += 1 return xfx except KeyError: pass # Check for a column xf_index try: xfx = self.colinfo_map[colx].xf_index if xfx == -1: xfx = 15 self._xf_index_stats[2] += 1 return xfx except KeyError: # If all else fails, 15 is used as hardwired global default xf_index. self._xf_index_stats[3] += 1 return 15 def row_len(self, rowx): """ Returns the effective number of cells in the given row. For use with ``open_workbook(ragged_rows=True)`` which is likely to produce rows with fewer than :attr:`~Sheet.ncols` cells. .. versionadded:: 0.7.2 """ return len(self._cell_values[rowx]) def row(self, rowx): """ Returns a sequence of the :class:`Cell` objects in the given row. """ return [ self.cell(rowx, colx) for colx in xrange(len(self._cell_values[rowx])) ] def __getitem__(self, item): """ Takes either rowindex or (rowindex, colindex) as an index, and returns either row or cell respectively. """ try: rowix, colix = item except TypeError: # it's not a tuple (or of right size), let's try indexing as is # if this is a problem, let this error propagate back return self.row(item) else: return self.cell(rowix, colix) def get_rows(self): "Returns a generator for iterating through each row." return (self.row(index) for index in range(self.nrows)) # makes `for row in sheet` natural and intuitive __iter__ = get_rows def row_types(self, rowx, start_colx=0, end_colx=None): """ Returns a slice of the types of the cells in the given row. """ if end_colx is None: return self._cell_types[rowx][start_colx:] return self._cell_types[rowx][start_colx:end_colx] def row_values(self, rowx, start_colx=0, end_colx=None): """ Returns a slice of the values of the cells in the given row. """ if end_colx is None: return self._cell_values[rowx][start_colx:] return self._cell_values[rowx][start_colx:end_colx] def row_slice(self, rowx, start_colx=0, end_colx=None): """ Returns a slice of the :class:`Cell` objects in the given row. """ nc = len(self._cell_values[rowx]) if start_colx < 0: start_colx += nc if start_colx < 0: start_colx = 0 if end_colx is None or end_colx > nc: end_colx = nc elif end_colx < 0: end_colx += nc return [ self.cell(rowx, colx) for colx in xrange(start_colx, end_colx) ] def col_slice(self, colx, start_rowx=0, end_rowx=None): """ Returns a slice of the :class:`Cell` objects in the given column. """ nr = self.nrows if start_rowx < 0: start_rowx += nr if start_rowx < 0: start_rowx = 0 if end_rowx is None or end_rowx > nr: end_rowx = nr elif end_rowx < 0: end_rowx += nr return [ self.cell(rowx, colx) for rowx in xrange(start_rowx, end_rowx) ] def col_values(self, colx, start_rowx=0, end_rowx=None): """ Returns a slice of the values of the cells in the given column. """ nr = self.nrows if start_rowx < 0: start_rowx += nr if start_rowx < 0: start_rowx = 0 if end_rowx is None or end_rowx > nr: end_rowx = nr elif end_rowx < 0: end_rowx += nr return [ self._cell_values[rowx][colx] for rowx in xrange(start_rowx, end_rowx) ] def col_types(self, colx, start_rowx=0, end_rowx=None): """ Returns a slice of the types of the cells in the given column. """ nr = self.nrows if start_rowx < 0: start_rowx += nr if start_rowx < 0: start_rowx = 0 if end_rowx is None or end_rowx > nr: end_rowx = nr elif end_rowx < 0: end_rowx += nr return [ self._cell_types[rowx][colx] for rowx in xrange(start_rowx, end_rowx) ] col = col_slice # === Following methods are used in building the worksheet. # === They are not part of the API. def tidy_dimensions(self): if self.verbosity >= 3: fprintf( self.logfile, "tidy_dimensions: nrows=%d ncols=%d \n", self.nrows, self.ncols, ) if 1 and self.merged_cells: nr = nc = 0 umaxrows = self.utter_max_rows umaxcols = self.utter_max_cols for crange in self.merged_cells: rlo, rhi, clo, chi = crange if not (0 <= rlo < rhi <= umaxrows) or not (0 <= clo < chi <= umaxcols): fprintf(self.logfile, "*** WARNING: sheet #%d (%r), MERGEDCELLS bad range %r\n", self.number, self.name, crange) if rhi > nr: nr = rhi if chi > nc: nc = chi if nc > self.ncols: self.ncols = nc self._first_full_rowx = -2 if nr > self.nrows: # we put one empty cell at (nr-1,0) to make sure # we have the right number of rows. The ragged rows # will sort out the rest if needed. self.put_cell(nr-1, 0, XL_CELL_EMPTY, UNICODE_LITERAL(''), -1) if (self.verbosity >= 1 and (self.nrows != self._dimnrows or self.ncols != self._dimncols)): fprintf( self.logfile, "NOTE *** sheet %d (%r): DIMENSIONS R,C = %d,%d should be %d,%d\n", self.number, self.name, self._dimnrows, self._dimncols, self.nrows, self.ncols, ) if not self.ragged_rows: # fix ragged rows ncols = self.ncols s_cell_types = self._cell_types s_cell_values = self._cell_values s_cell_xf_indexes = self._cell_xf_indexes s_fmt_info = self.formatting_info # for rowx in xrange(self.nrows): if self._first_full_rowx == -2: ubound = self.nrows else: ubound = self._first_full_rowx for rowx in xrange(ubound): trow = s_cell_types[rowx] rlen = len(trow) nextra = ncols - rlen if nextra > 0: s_cell_values[rowx][rlen:] = [UNICODE_LITERAL('')] * nextra trow[rlen:] = self.bt * nextra if s_fmt_info: s_cell_xf_indexes[rowx][rlen:] = self.bf * nextra def put_cell_ragged(self, rowx, colx, ctype, value, xf_index): if ctype is None: # we have a number, so look up the cell type ctype = self._xf_index_to_xl_type_map[xf_index] assert 0 <= colx < self.utter_max_cols assert 0 <= rowx < self.utter_max_rows fmt_info = self.formatting_info try: nr = rowx + 1 if self.nrows < nr: scta = self._cell_types.append scva = self._cell_values.append scxa = self._cell_xf_indexes.append bt = self.bt bf = self.bf for _unused in xrange(self.nrows, nr): scta(bt * 0) scva([]) if fmt_info: scxa(bf * 0) self.nrows = nr types_row = self._cell_types[rowx] values_row = self._cell_values[rowx] if fmt_info: fmt_row = self._cell_xf_indexes[rowx] ltr = len(types_row) if colx >= self.ncols: self.ncols = colx + 1 num_empty = colx - ltr if not num_empty: # most common case: colx == previous colx + 1 # self._put_cell_cells_appended += 1 types_row.append(ctype) values_row.append(value) if fmt_info: fmt_row.append(xf_index) return if num_empty > 0: num_empty += 1 # self._put_cell_row_widenings += 1 # types_row.extend(self.bt * num_empty) # values_row.extend([UNICODE_LITERAL('')] * num_empty) # if fmt_info: # fmt_row.extend(self.bf * num_empty) types_row[ltr:] = self.bt * num_empty values_row[ltr:] = [UNICODE_LITERAL('')] * num_empty if fmt_info: fmt_row[ltr:] = self.bf * num_empty types_row[colx] = ctype values_row[colx] = value if fmt_info: fmt_row[colx] = xf_index except: print("put_cell", rowx, colx, file=self.logfile) raise def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): if ctype is None: # we have a number, so look up the cell type ctype = self._xf_index_to_xl_type_map[xf_index] # assert 0 <= colx < self.utter_max_cols # assert 0 <= rowx < self.utter_max_rows try: self._cell_types[rowx][colx] = ctype self._cell_values[rowx][colx] = value if self.formatting_info: self._cell_xf_indexes[rowx][colx] = xf_index except IndexError: # print >> self.logfile, "put_cell extending", rowx, colx # self.extend_cells(rowx+1, colx+1) # self._put_cell_exceptions += 1 nr = rowx + 1 nc = colx + 1 assert 1 <= nc <= self.utter_max_cols assert 1 <= nr <= self.utter_max_rows if nc > self.ncols: self.ncols = nc # The row self._first_full_rowx and all subsequent rows # are guaranteed to have length == self.ncols. Thus the # "fix ragged rows" section of the tidy_dimensions method # doesn't need to examine them. if nr < self.nrows: # cell data is not in non-descending row order *AND* # self.ncols has been bumped up. # This very rare case ruins this optimisation. self._first_full_rowx = -2 elif rowx > self._first_full_rowx > -2: self._first_full_rowx = rowx if nr <= self.nrows: # New cell is in an existing row, so extend that row (if necessary). # Note that nr < self.nrows means that the cell data # is not in ascending row order!! trow = self._cell_types[rowx] nextra = self.ncols - len(trow) if nextra > 0: # self._put_cell_row_widenings += 1 trow.extend(self.bt * nextra) if self.formatting_info: self._cell_xf_indexes[rowx].extend(self.bf * nextra) self._cell_values[rowx].extend([UNICODE_LITERAL('')] * nextra) else: scta = self._cell_types.append scva = self._cell_values.append scxa = self._cell_xf_indexes.append fmt_info = self.formatting_info nc = self.ncols bt = self.bt bf = self.bf for _unused in xrange(self.nrows, nr): # self._put_cell_rows_appended += 1 scta(bt * nc) scva([UNICODE_LITERAL('')] * nc) if fmt_info: scxa(bf * nc) self.nrows = nr # === end of code from extend_cells() try: self._cell_types[rowx][colx] = ctype self._cell_values[rowx][colx] = value if self.formatting_info: self._cell_xf_indexes[rowx][colx] = xf_index except: print("put_cell", rowx, colx, file=self.logfile) raise except: print("put_cell", rowx, colx, file=self.logfile) raise # === Methods after this line neither know nor care about how cells are stored. def read(self, bk): global rc_stats DEBUG = 0 blah = DEBUG or self.verbosity >= 2 blah_rows = DEBUG or self.verbosity >= 4 blah_formulas = 0 and blah r1c1 = 0 oldpos = bk._position bk._position = self._position XL_SHRFMLA_ETC_ETC = ( XL_SHRFMLA, XL_ARRAY, XL_TABLEOP, XL_TABLEOP2, XL_ARRAY2, XL_TABLEOP_B2, ) self_put_cell = self.put_cell local_unpack = unpack bk_get_record_parts = bk.get_record_parts bv = self.biff_version fmt_info = self.formatting_info do_sst_rich_text = fmt_info and bk._rich_text_runlist_map rowinfo_sharing_dict = {} txos = {} eof_found = 0 while 1: # if DEBUG: print "SHEET.READ: about to read from position %d" % bk._position rc, data_len, data = bk_get_record_parts() # if rc in rc_stats: # rc_stats[rc] += 1 # else: # rc_stats[rc] = 1 # if DEBUG: print "SHEET.READ: op 0x%04x, %d bytes %r" % (rc, data_len, data) if rc == XL_NUMBER: # [:14] in following stmt ignores extraneous rubbish at end of record. # Sample file testEON-8.xls supplied by Jan Kraus. rowx, colx, xf_index, d = local_unpack('> 15) & 1 r.outline_level = bits2 & 7 r.outline_group_starts_ends = (bits2 >> 4) & 1 r.hidden = (bits2 >> 5) & 1 r.height_mismatch = (bits2 >> 6) & 1 r.has_default_xf_index = (bits2 >> 7) & 1 r.xf_index = (bits2 >> 16) & 0xfff r.additional_space_above = (bits2 >> 28) & 1 r.additional_space_below = (bits2 >> 29) & 1 if not r.has_default_xf_index: r.xf_index = -1 self.rowinfo_map[rowx] = r if 0 and r.xf_index > -1: fprintf(self.logfile, "**ROW %d %d %d\n", self.number, rowx, r.xf_index) if blah_rows: print('ROW', rowx, bits1, bits2, file=self.logfile) r.dump(self.logfile, header="--- sh #%d, rowx=%d ---" % (self.number, rowx)) elif rc in XL_FORMULA_OPCODES: # 06, 0206, 0406 # DEBUG = 1 # if DEBUG: print "FORMULA: rc: 0x%04x data: %r" % (rc, data) if bv >= 50: rowx, colx, xf_index, result_str, flags = local_unpack('= 30: rowx, colx, xf_index, result_str, flags = local_unpack(' 255: break # Excel does 0 to 256 inclusive self.colinfo_map[colx] = c if 0: fprintf(self.logfile, "**COL %d %d %d\n", self.number, colx, c.xf_index) if blah: fprintf( self.logfile, "COLINFO sheet #%d cols %d-%d: wid=%d xf_index=%d flags=0x%04x\n", self.number, first_colx, last_colx, c.width, c.xf_index, flags, ) c.dump(self.logfile, header='===') elif rc == XL_DEFCOLWIDTH: self.defcolwidth, = local_unpack(">= 1 self.gcw = tuple(gcw) if 0: showgcw = "".join(map(lambda x: "F "[x], gcw)).rstrip().replace(' ', '.') print("GCW:", showgcw, file=self.logfile) elif rc == XL_BLANK: if not fmt_info: continue rowx, colx, xf_index = local_unpack('> self.logfile, "BLANK", rowx, colx, xf_index self_put_cell(rowx, colx, XL_CELL_BLANK, '', xf_index) elif rc == XL_MULBLANK: # 00BE if not fmt_info: continue nitems = data_len >> 1 result = local_unpack("<%dH" % nitems, data) rowx, mul_first = result[:2] mul_last = result[-1] # print >> self.logfile, "MULBLANK", rowx, mul_first, mul_last, data_len, nitems, mul_last + 4 - mul_first assert nitems == mul_last + 4 - mul_first pos = 2 for colx in xrange(mul_first, mul_last + 1): self_put_cell(rowx, colx, XL_CELL_BLANK, '', result[pos]) pos += 1 elif rc == XL_DIMENSION or rc == XL_DIMENSION2: if data_len == 0: # Four zero bytes after some other record. See github issue 64. continue # if data_len == 10: # Was crashing on BIFF 4.0 file w/o the two trailing unused bytes. # Reported by Ralph Heimburger. if bv < 80: dim_tuple = local_unpack(' found EOF", file=self.logfile) elif rc == XL_COUNTRY: bk.handle_country(data) elif rc == XL_LABELRANGES: pos = 0 pos = unpack_cell_range_address_list_update_pos( self.row_label_ranges, data, pos, bv, addr_size=8, ) pos = unpack_cell_range_address_list_update_pos( self.col_label_ranges, data, pos, bv, addr_size=8, ) assert pos == data_len elif rc == XL_ARRAY: row1x, rownx, col1x, colnx, array_flags, tokslen = \ local_unpack("= 80 num_CFs, needs_recalc, browx1, browx2, bcolx1, bcolx2 = \ unpack("<6H", data[0:12]) if self.verbosity >= 1: fprintf( self.logfile, "\n*** WARNING: Ignoring CONDFMT (conditional formatting) record\n" "*** in Sheet %d (%r).\n" "*** %d CF record(s); needs_recalc_or_redraw = %d\n" "*** Bounding box is %s\n", self.number, self.name, num_CFs, needs_recalc, rangename2d(browx1, browx2+1, bcolx1, bcolx2+1), ) olist = [] # updated by the function pos = unpack_cell_range_address_list_update_pos( olist, data, 12, bv, addr_size=8) # print >> self.logfile, repr(result), len(result) if self.verbosity >= 1: fprintf( self.logfile, "*** %d individual range(s):\n" "*** %s\n", len(olist), ", ".join(rangename2d(*coords) for coords in olist), ) elif rc == XL_CF: if not fmt_info: continue cf_type, cmp_op, sz1, sz2, flags = unpack("> 26) & 1 bord_block = (flags >> 28) & 1 patt_block = (flags >> 29) & 1 if self.verbosity >= 1: fprintf( self.logfile, "\n*** WARNING: Ignoring CF (conditional formatting) sub-record.\n" "*** cf_type=%d, cmp_op=%d, sz1=%d, sz2=%d, flags=0x%08x\n" "*** optional data blocks: font=%d, border=%d, pattern=%d\n", cf_type, cmp_op, sz1, sz2, flags, font_block, bord_block, patt_block, ) # hex_char_dump(data, 0, data_len, fout=self.logfile) pos = 12 if font_block: (font_height, font_options, weight, escapement, underline, font_colour_index, two_bits, font_esc, font_underl) = unpack("<64x i i H H B 3x i 4x i i i 18x", data[pos:pos+118]) font_style = (two_bits > 1) & 1 posture = (font_options > 1) & 1 font_canc = (two_bits > 7) & 1 cancellation = (font_options > 7) & 1 if self.verbosity >= 1: fprintf( self.logfile, "*** Font info: height=%d, weight=%d, escapement=%d,\n" "*** underline=%d, colour_index=%d, esc=%d, underl=%d,\n" "*** style=%d, posture=%d, canc=%d, cancellation=%d\n", font_height, weight, escapement, underline, font_colour_index, font_esc, font_underl, font_style, posture, font_canc, cancellation, ) pos += 118 if bord_block: pos += 8 if patt_block: pos += 4 fmla1 = data[pos:pos+sz1] pos += sz1 if blah and sz1: fprintf(self.logfile, "*** formula 1:\n") dump_formula(bk, fmla1, sz1, bv, reldelta=0, blah=1) fmla2 = data[pos:pos+sz2] pos += sz2 assert pos == data_len if blah and sz2: fprintf(self.logfile, "*** formula 2:\n") dump_formula(bk, fmla2, sz2, bv, reldelta=0, blah=1) elif rc == XL_DEFAULTROWHEIGHT: if data_len == 4: bits, self.default_row_height = unpack("> 1) & 1 self.default_additional_space_above = (bits >> 2) & 1 self.default_additional_space_below = (bits >> 3) & 1 elif rc == XL_MERGEDCELLS: if not fmt_info: continue pos = unpack_cell_range_address_list_update_pos( self.merged_cells, data, 0, bv, addr_size=8) if blah: fprintf(self.logfile, "MERGEDCELLS: %d ranges\n", (pos - 2) // 8) assert pos == data_len, \ "MERGEDCELLS: pos=%d data_len=%d" % (pos, data_len) elif rc == XL_WINDOW2: if bv >= 80 and data_len >= 14: ( options, self.first_visible_rowx, self.first_visible_colx, self.gridline_colour_index, self.cached_page_break_preview_mag_factor, self.cached_normal_view_mag_factor ) = unpack("= 30 # BIFF3-7 ( options, self.first_visible_rowx, self.first_visible_colx, ) = unpack(">= 1 elif rc == XL_SCL: num, den = unpack("= 0: print( "WARNING *** SCL rcd sheet %d: should have 0.1 <= num/den <= 4; got %d/%d" % (self.number, num, den), file=self.logfile, ) result = 100 self.scl_mag_factor = result elif rc == XL_PANE: ( self.vert_split_pos, self.horz_split_pos, self.horz_split_first_visible, self.vert_split_first_visible, self.split_active_pane, ) = unpack("= 80)) + 2 == data_len pos = 2 if bv < 80: while pos < data_len: self.horizontal_page_breaks.append((local_unpack("= 80)) + 2 == data_len pos = 2 if bv < 80: while pos < data_len: self.vertical_page_breaks.append((local_unpack("> 15) & 1 r.has_default_xf_index = bits2 & 1 r.xf_index = xf_index # r.outline_level = 0 # set in __init__ # r.outline_group_starts_ends = 0 # set in __init__ # r.hidden = 0 # set in __init__ # r.height_mismatch = 0 # set in __init__ # r.additional_space_above = 0 # set in __init__ # r.additional_space_below = 0 # set in __init__ self.rowinfo_map[rowx] = r if 0 and r.xf_index > -1: fprintf(self.logfile, "**ROW %d %d %d\n", self.number, rowx, r.xf_index) if blah_rows: print('ROW_B2', rowx, bits1, file=self.logfile) r.dump(self.logfile, header="--- sh #%d, rowx=%d ---" % (self.number, rowx)) elif rc == XL_COLWIDTH: # BIFF2 only if not fmt_info: continue first_colx, last_colx, width\ = local_unpack("= 30) + 1 nchars_expected = unpack("<" + "BH"[lenlen - 1], data[:lenlen])[0] offset = lenlen if bv < 80: enc = bk.encoding or bk.derive_encoding() nchars_found = 0 result = UNICODE_LITERAL("") while 1: if bv >= 80: flag = BYTES_ORD(data[offset]) & 1 enc = ("latin_1", "utf_16_le")[flag] offset += 1 chunk = unicode(data[offset:], enc) result += chunk nchars_found += len(chunk) if nchars_found == nchars_expected: return result if nchars_found > nchars_expected: msg = ("STRING/CONTINUE: expected %d chars, found %d" % (nchars_expected, nchars_found)) raise XLRDError(msg) rc, _unused_len, data = bk.get_record_parts() if rc != XL_CONTINUE: raise XLRDError( "Expected CONTINUE record; found record-type 0x%04X" % rc) offset = 0 def update_cooked_mag_factors(self): # Cached values are used ONLY for the non-active view mode. # When the user switches to the non-active view mode, # if the cached value for that mode is not valid, # Excel pops up a window which says: # "The number must be between 10 and 400. Try again by entering a number in this range." # When the user hits OK, it drops into the non-active view mode # but uses the magn from the active mode. # NOTE: definition of "valid" depends on mode ... see below blah = DEBUG or self.verbosity > 0 if self.show_in_page_break_preview: if self.scl_mag_factor is None: # no SCL record self.cooked_page_break_preview_mag_factor = 100 # Yes, 100, not 60, NOT a typo else: self.cooked_page_break_preview_mag_factor = self.scl_mag_factor zoom = self.cached_normal_view_mag_factor if not (10 <= zoom <=400): if blah: print( "WARNING *** WINDOW2 rcd sheet %d: Bad cached_normal_view_mag_factor: %d" % (self.number, self.cached_normal_view_mag_factor), file=self.logfile, ) zoom = self.cooked_page_break_preview_mag_factor self.cooked_normal_view_mag_factor = zoom else: # normal view mode if self.scl_mag_factor is None: # no SCL record self.cooked_normal_view_mag_factor = 100 else: self.cooked_normal_view_mag_factor = self.scl_mag_factor zoom = self.cached_page_break_preview_mag_factor if not zoom: # VALID, defaults to 60 zoom = 60 elif not (10 <= zoom <= 400): if blah: print( "WARNING *** WINDOW2 rcd sheet %r: Bad cached_page_break_preview_mag_factor: %r" % (self.number, self.cached_page_break_preview_mag_factor), file=self.logfile, ) zoom = self.cooked_normal_view_mag_factor self.cooked_page_break_preview_mag_factor = zoom def fixed_BIFF2_xfindex(self, cell_attr, rowx, colx, true_xfx=None): DEBUG = 0 blah = DEBUG or self.verbosity >= 2 if self.biff_version == 21: if self.book.xf_list: if true_xfx is not None: xfx = true_xfx else: xfx = BYTES_ORD(cell_attr[0]) & 0x3F if xfx == 0x3F: if self._ixfe is None: raise XLRDError("BIFF2 cell record has XF index 63 but no preceding IXFE record.") xfx = self._ixfe # OOo docs are capable of interpretation that each # cell record is preceded immediately by its own IXFE record. # Empirical evidence is that (sensibly) an IXFE record applies to all # following cell records until another IXFE comes along. return xfx # Have either Excel 2.0, or broken 2.1 w/o XF records -- same effect. self.biff_version = self.book.biff_version = 20 #### check that XF slot in cell_attr is zero xfx_slot = BYTES_ORD(cell_attr[0]) & 0x3F assert xfx_slot == 0 xfx = self._cell_attr_to_xfx.get(cell_attr) if xfx is not None: return xfx if blah: fprintf(self.logfile, "New cell_attr %r at (%r, %r)\n", cell_attr, rowx, colx) if not self.book.xf_list: for xfx in xrange(16): self.insert_new_BIFF20_xf(cell_attr=b"\x40\x00\x00", style=xfx < 15) xfx = self.insert_new_BIFF20_xf(cell_attr=cell_attr) return xfx def insert_new_BIFF20_xf(self, cell_attr, style=0): DEBUG = 0 blah = DEBUG or self.verbosity >= 2 book = self.book xfx = len(book.xf_list) xf = self.fake_XF_from_BIFF20_cell_attr(cell_attr, style) xf.xf_index = xfx book.xf_list.append(xf) if blah: xf.dump(self.logfile, header="=== Faked XF %d ===" % xfx, footer="======") if xf.format_key not in book.format_map: if xf.format_key: msg = "ERROR *** XF[%d] unknown format key (%d, 0x%04x)\n" fprintf(self.logfile, msg, xf.xf_index, xf.format_key, xf.format_key) fmt = Format(xf.format_key, FUN, UNICODE_LITERAL("General")) book.format_map[xf.format_key] = fmt book.format_list.append(fmt) cellty_from_fmtty = { FNU: XL_CELL_NUMBER, FUN: XL_CELL_NUMBER, FGE: XL_CELL_NUMBER, FDT: XL_CELL_DATE, FTX: XL_CELL_NUMBER, # Yes, a number can be formatted as text. } fmt = book.format_map[xf.format_key] cellty = cellty_from_fmtty[fmt.type] self._xf_index_to_xl_type_map[xf.xf_index] = cellty self._cell_attr_to_xfx[cell_attr] = xfx return xfx def fake_XF_from_BIFF20_cell_attr(self, cell_attr, style=0): from .formatting import XF, XFAlignment, XFBorder, XFBackground, XFProtection xf = XF() xf.alignment = XFAlignment() xf.alignment.indent_level = 0 xf.alignment.shrink_to_fit = 0 xf.alignment.text_direction = 0 xf.border = XFBorder() xf.border.diag_up = 0 xf.border.diag_down = 0 xf.border.diag_colour_index = 0 xf.border.diag_line_style = 0 # no line xf.background = XFBackground() xf.protection = XFProtection() (prot_bits, font_and_format, halign_etc) = unpack('> 6 upkbits(xf.protection, prot_bits, ( (6, 0x40, 'cell_locked'), (7, 0x80, 'formula_hidden'), )) xf.alignment.hor_align = halign_etc & 0x07 for mask, side in ((0x08, 'left'), (0x10, 'right'), (0x20, 'top'), (0x40, 'bottom')): if halign_etc & mask: colour_index, line_style = 8, 1 # black, thin else: colour_index, line_style = 0, 0 # none, none setattr(xf.border, side + '_colour_index', colour_index) setattr(xf.border, side + '_line_style', line_style) bg = xf.background if halign_etc & 0x80: bg.fill_pattern = 17 else: bg.fill_pattern = 0 bg.background_colour_index = 9 # white bg.pattern_colour_index = 8 # black xf.parent_style_index = (0x0FFF, 0)[style] xf.alignment.vert_align = 2 # bottom xf.alignment.rotation = 0 attr_stems = [ 'format', 'font', 'alignment', 'border', 'background', 'protection', ] for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, 1) return xf def req_fmt_info(self): if not self.formatting_info: raise XLRDError("Feature requires open_workbook(..., formatting_info=True)") def computed_column_width(self, colx): """ Determine column display width. :param colx: Index of the queried column, range 0 to 255. Note that it is possible to find out the width that will be used to display columns with no cell information e.g. column IV (colx=255). :return: The column width that will be used for displaying the given column by Excel, in units of 1/256th of the width of a standard character (the digit zero in the first font). .. versionadded:: 0.6.1 """ self.req_fmt_info() if self.biff_version >= 80: colinfo = self.colinfo_map.get(colx, None) if colinfo is not None: return colinfo.width if self.standardwidth is not None: return self.standardwidth elif self.biff_version >= 40: if self.gcw[colx]: if self.standardwidth is not None: return self.standardwidth else: colinfo = self.colinfo_map.get(colx, None) if colinfo is not None: return colinfo.width elif self.biff_version == 30: colinfo = self.colinfo_map.get(colx, None) if colinfo is not None: return colinfo.width # All roads lead to Rome and the DEFCOLWIDTH ... if self.defcolwidth is not None: return self.defcolwidth * 256 return 8 * 256 # 8 is what Excel puts in a DEFCOLWIDTH record def handle_hlink(self, data): # DEBUG = 1 if DEBUG: print("\n=== hyperlink ===", file=self.logfile) record_size = len(data) h = Hyperlink() h.frowx, h.lrowx, h.fcolx, h.lcolx, guid0, dummy, options = unpack(' 0: fprintf( self.logfile, "*** WARNING: hyperlink at R%dC%d has %d extra data bytes: %s\n", h.frowx + 1, h.fcolx + 1, extra_nbytes, REPR(data[-extra_nbytes:]), ) # Seen: b"\x00\x00" also b"A\x00", b"V\x00" elif extra_nbytes < 0: raise XLRDError("Bug or corrupt file, send copy of input file for debugging") self.hyperlink_list.append(h) for rowx in xrange(h.frowx, h.lrowx+1): for colx in xrange(h.fcolx, h.lcolx+1): self.hyperlink_map[rowx, colx] = h def handle_quicktip(self, data): rcx, frowx, lrowx, fcolx, lcolx = unpack('<5H', data[:10]) assert rcx == XL_QUICKTIP assert self.hyperlink_list h = self.hyperlink_list[-1] assert (frowx, lrowx, fcolx, lcolx) == (h.frowx, h.lrowx, h.fcolx, h.lcolx) assert data[-2:] == b'\x00\x00' h.quicktip = unicode(data[10:-2], 'utf_16_le') def handle_msodrawingetc(self, recid, data_len, data): if not OBJ_MSO_DEBUG: return DEBUG = 1 if self.biff_version < 80: return o = MSODrawing() pos = 0 while pos < data_len: tmp, fbt, cb = unpack('> 4) & 0xFFF if ver == 0xF: ndb = 0 # container else: ndb = cb if DEBUG: hex_char_dump(data, pos, ndb + 8, base=0, fout=self.logfile) fprintf(self.logfile, "fbt:0x%04X inst:%d ver:0x%X cb:%d (0x%04X)\n", fbt, inst, ver, cb, cb) if fbt == 0xF010: # Client Anchor assert ndb == 18 (o.anchor_unk, o.anchor_colx_lo, o.anchor_rowx_lo, o.anchor_colx_hi, o.anchor_rowx_hi) = unpack(' 0: rc2, data2_len, data2 = self.book.get_record_parts() assert rc2 == XL_NOTE dummy_rowx, nb = unpack('> 1) & 1 o.row_hidden = (option_flags >> 7) & 1 o.col_hidden = (option_flags >> 8) & 1 # XL97 dev kit book says NULL [sic] bytes padding between string count and string data # to ensure that string is word-aligned. Appears to be nonsense. o.author, endpos = unpack_unicode_update_pos(data, 8, lenlen=2) # There is a random/undefined byte after the author string (not counted in the # string length). # Issue 4 on github: Google Spreadsheet doesn't write the undefined byte. assert (data_len - endpos) in (0, 1) if OBJ_MSO_DEBUG: o.dump(self.logfile, header="=== Note ===", footer= " ") txo = txos.get(o._object_id) if txo: o.text = txo.text o.rich_text_runlist = txo.rich_text_runlist self.cell_note_map[o.rowx, o.colx] = o def handle_txo(self, data): if self.biff_version < 80: return o = MSTxo() fmt = '2}:<{}>".format(self.number, self.name) class MSODrawing(BaseObject): pass class MSObj(BaseObject): pass class MSTxo(BaseObject): pass class Note(BaseObject): """ Represents a user "comment" or "note". Note objects are accessible through :attr:`Sheet.cell_note_map`. .. versionadded:: 0.7.2 """ #: Author of note author = UNICODE_LITERAL('') #: ``True`` if the containing column is hidden col_hidden = 0 #: Column index colx = 0 #: List of ``(offset_in_string, font_index)`` tuples. #: Unlike :attr:`Sheet.rich_text_runlist_map`, the first offset should #: always be 0. rich_text_runlist = None #: True if the containing row is hidden row_hidden = 0 #: Row index rowx = 0 #: True if note is always shown show = 0 #: Text of the note text = UNICODE_LITERAL('') class Hyperlink(BaseObject): """ Contains the attributes of a hyperlink. Hyperlink objects are accessible through :attr:`Sheet.hyperlink_list` and :attr:`Sheet.hyperlink_map`. .. versionadded:: 0.7.2 """ #: Index of first row frowx = None #: Index of last row lrowx = None #: Index of first column fcolx = None #: Index of last column lcolx = None #: Type of hyperlink. Unicode string, one of 'url', 'unc', #: 'local file', 'workbook', 'unknown' type = None #: The URL or file-path, depending in the type. Unicode string, except #: in the rare case of a local but non-existent file with non-ASCII #: characters in the name, in which case only the "8.3" filename is #: available, as a :class:`bytes` (3.x) or :class:`str` (2.x) string, #: *with unknown encoding.* url_or_path = None #: Description. #: This is displayed in the cell, #: and should be identical to the cell value. Unicode string, or ``None``. #: It seems impossible NOT to have a description created by the Excel UI. desc = None #: Target frame. Unicode string. #: #: .. note:: #: No cases of this have been seen in the wild. #: It seems impossible to create one in the Excel UI. target = None #: The piece after the "#" in #: "http://docs.python.org/library#struct_module", or the ``Sheet1!A1:Z99`` #: part when type is "workbook". textmark = None #: The text of the "quick tip" displayed when the cursor #: hovers over the hyperlink. quicktip = None # === helpers === def unpack_RK(rk_str): flags = BYTES_ORD(rk_str[0]) if flags & 2: # There's a SIGNED 30-bit integer in there! i, = unpack('>= 2 # div by 4 to drop the 2 flag bits if flags & 1: return i / 100.0 return float(i) else: # It's the most significant 30 bits of an IEEE 754 64-bit FP number d, = unpack(' Type symbol Type number Python value XL_CELL_EMPTY 0 empty string '' XL_CELL_TEXT 1 a Unicode string XL_CELL_NUMBER 2 float XL_CELL_DATE 3 float XL_CELL_BOOLEAN 4 int; 1 means TRUE, 0 means FALSE XL_CELL_ERROR 5 int representing internal Excel codes; for a text representation, refer to the supplied dictionary error_text_from_code XL_CELL_BLANK 6 empty string ''. Note: this type will appear only when open_workbook(..., formatting_info=True) is used. """ __slots__ = ['ctype', 'value', 'xf_index'] def __init__(self, ctype, value, xf_index=None): self.ctype = ctype self.value = value self.xf_index = xf_index def __repr__(self): if self.xf_index is None: return "%s:%r" % (ctype_text[self.ctype], self.value) else: return "%s:%r (XF:%r)" % (ctype_text[self.ctype], self.value, self.xf_index) empty_cell = Cell(XL_CELL_EMPTY, UNICODE_LITERAL('')) ##### =============== Colinfo and Rowinfo ============================== ##### class Colinfo(BaseObject): """ Width and default formatting information that applies to one or more columns in a sheet. Derived from ``COLINFO`` records. Here is the default hierarchy for width, according to the OOo docs: In BIFF3, if a ``COLINFO`` record is missing for a column, the width specified in the record ``DEFCOLWIDTH`` is used instead. In BIFF4-BIFF7, the width set in this ``COLINFO`` record is only used, if the corresponding bit for this column is cleared in the ``GCW`` record, otherwise the column width set in the ``DEFCOLWIDTH`` record is used (the ``STANDARDWIDTH`` record is always ignored in this case [#f1]_). In BIFF8, if a ``COLINFO`` record is missing for a column, the width specified in the record ``STANDARDWIDTH`` is used. If this ``STANDARDWIDTH`` record is also missing, the column width of the record ``DEFCOLWIDTH`` is used instead. .. [#f1] The docs on the ``GCW`` record say this: If a bit is set, the corresponding column uses the width set in the ``STANDARDWIDTH`` record. If a bit is cleared, the corresponding column uses the width set in the ``COLINFO`` record for this column. If a bit is set, and the worksheet does not contain the ``STANDARDWIDTH`` record, or if the bit is cleared, and the worksheet does not contain the ``COLINFO`` record, the ``DEFCOLWIDTH`` record of the worksheet will be used instead. xlrd goes with the GCW version of the story. Reference to the source may be useful: see :meth:`Sheet.computed_column_width`. .. versionadded:: 0.6.1 """ #: Width of the column in 1/256 of the width of the zero character, #: using default font (first ``FONT`` record in the file). width = 0 #: XF index to be used for formatting empty cells. xf_index = -1 #: 1 = column is hidden hidden = 0 #: Value of a 1-bit flag whose purpose is unknown #: but is often seen set to 1 bit1_flag = 0 #: Outline level of the column, in ``range(7)``. #: (0 = no outline) outline_level = 0 #: 1 = column is collapsed collapsed = 0 _USE_SLOTS = 1 class Rowinfo(BaseObject): """ Height and default formatting information that applies to a row in a sheet. Derived from ``ROW`` records. .. versionadded:: 0.6.1 """ if _USE_SLOTS: __slots__ = ( "height", "has_default_height", "outline_level", "outline_group_starts_ends", "hidden", "height_mismatch", "has_default_xf_index", "xf_index", "additional_space_above", "additional_space_below", ) def __init__(self): #: Height of the row, in twips. One twip == 1/20 of a point. self.height = None #: 0 = Row has custom height; 1 = Row has default height. self.has_default_height = None #: Outline level of the row (0 to 7) self.outline_level = None #: 1 = Outline group starts or ends here (depending on where the #: outline buttons are located, see ``WSBOOL`` record, which is not #: parsed by xlrd), *and* is collapsed. self.outline_group_starts_ends = None #: 1 = Row is hidden (manually, or by a filter or outline group) self.hidden = None #: 1 = Row height and default font height do not match. self.height_mismatch = None #: 1 = the xf_index attribute is usable; 0 = ignore it. self.has_default_xf_index = None #: Index to default :class:`~xlrd.formatting.XF` record for empty cells #: in this row. Don't use this if ``has_default_xf_index == 0``. self.xf_index = None #: This flag is set if the upper border of at least one cell in this #: row or if the lower border of at least one cell in the row above is #: formatted with a thick line style. Thin and medium line styles are #: not taken into account. self.additional_space_above = None #: This flag is set if the lower border of at least one cell in this row #: or if the upper border of at least one cell in the row below is #: formatted with a medium or thick line style. Thin line styles are not #: taken into account. self.additional_space_below = None def __getstate__(self): return ( self.height, self.has_default_height, self.outline_level, self.outline_group_starts_ends, self.hidden, self.height_mismatch, self.has_default_xf_index, self.xf_index, self.additional_space_above, self.additional_space_below, ) def __setstate__(self, state): ( self.height, self.has_default_height, self.outline_level, self.outline_group_starts_ends, self.hidden, self.height_mismatch, self.has_default_xf_index, self.xf_index, self.additional_space_above, self.additional_space_below, ) = state xlrd-2.0.1/xlrd/timemachine.py000066400000000000000000000033351376464300000163000ustar00rootroot00000000000000## #

Copyright (c) 2006-2012 Stephen John Machin, Lingfo Pty Ltd

#

This module is part of the xlrd package, which is released under a BSD-style licence.

## # timemachine.py -- adaptation for single codebase. # Currently supported: 2.6 to 2.7, 3.2+ # usage: from timemachine import * from __future__ import print_function import sys python_version = sys.version_info[:2] # e.g. version 2.6 -> (2, 6) if python_version >= (3, 0): # Python 3 BYTES_LITERAL = lambda x: x.encode('latin1') UNICODE_LITERAL = lambda x: x BYTES_ORD = lambda byte: byte from io import BytesIO as BYTES_IO def fprintf(f, fmt, *vargs): fmt = fmt.replace("%r", "%a") if fmt.endswith('\n'): print(fmt[:-1] % vargs, file=f) else: print(fmt % vargs, end=' ', file=f) EXCEL_TEXT_TYPES = (str, bytes, bytearray) # xlwt: isinstance(obj, EXCEL_TEXT_TYPES) REPR = ascii xrange = range unicode = lambda b, enc: b.decode(enc) ensure_unicode = lambda s: s unichr = chr else: # Python 2 BYTES_LITERAL = lambda x: x UNICODE_LITERAL = lambda x: x.decode('latin1') BYTES_ORD = ord from cStringIO import StringIO as BYTES_IO def fprintf(f, fmt, *vargs): if fmt.endswith('\n'): print(fmt[:-1] % vargs, file=f) else: print(fmt % vargs, end=' ', file=f) try: EXCEL_TEXT_TYPES = basestring # xlwt: isinstance(obj, EXCEL_TEXT_TYPES) except NameError: EXCEL_TEXT_TYPES = (str, unicode) REPR = repr xrange = xrange # following used only to overcome 2.x ElementTree gimmick which # returns text as `str` if it's ascii, otherwise `unicode` ensure_unicode = unicode # used only in xlsx.py xlrd-2.0.1/xlrd/xldate.py000066400000000000000000000173761376464300000153100ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Copyright (c) 2005-2008 Stephen John Machin, Lingfo Pty Ltd # This module is part of the xlrd package, which is released under a # BSD-style licence. # No part of the content of this file was derived from the works of David Giffin. """ Tools for working with dates and times in Excel files. The conversion from ``days`` to ``(year, month, day)`` starts with an integral "julian day number" aka JDN. FWIW: - JDN 0 corresponds to noon on Monday November 24 in Gregorian year -4713. More importantly: - Noon on Gregorian 1900-03-01 (day 61 in the 1900-based system) is JDN 2415080.0 - Noon on Gregorian 1904-01-02 (day 1 in the 1904-based system) is JDN 2416482.0 """ import datetime _JDN_delta = (2415080 - 61, 2416482 - 1) assert _JDN_delta[1] - _JDN_delta[0] == 1462 # Pre-calculate the datetime epochs for efficiency. epoch_1904 = datetime.datetime(1904, 1, 1) epoch_1900 = datetime.datetime(1899, 12, 31) epoch_1900_minus_1 = datetime.datetime(1899, 12, 30) # This is equivalent to 10000-01-01: _XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) class XLDateError(ValueError): "A base class for all datetime-related errors." class XLDateNegative(XLDateError): "``xldate < 0.00``" class XLDateAmbiguous(XLDateError): "The 1900 leap-year problem ``(datemode == 0 and 1.0 <= xldate < 61.0)``" class XLDateTooLarge(XLDateError): "Gregorian year 10000 or later" class XLDateBadDatemode(XLDateError): "``datemode`` arg is neither 0 nor 1" class XLDateBadTuple(XLDateError): pass def xldate_as_tuple(xldate, datemode): """ Convert an Excel number (presumed to represent a date, a datetime or a time) into a tuple suitable for feeding to datetime or mx.DateTime constructors. :param xldate: The Excel number :param datemode: 0: 1900-based, 1: 1904-based. :raises xlrd.xldate.XLDateNegative: :raises xlrd.xldate.XLDateAmbiguous: :raises xlrd.xldate.XLDateTooLarge: :raises xlrd.xldate.XLDateBadDatemode: :raises xlrd.xldate.XLDateError: :returns: Gregorian ``(year, month, day, hour, minute, nearest_second)``. .. warning:: When using this function to interpret the contents of a workbook, you should pass in the :attr:`~xlrd.book.Book.datemode` attribute of that workbook. Whether the workbook has ever been anywhere near a Macintosh is irrelevant. .. admonition:: Special case If ``0.0 <= xldate < 1.0``, it is assumed to represent a time; ``(0, 0, 0, hour, minute, second)`` will be returned. .. note:: ``1904-01-01`` is not regarded as a valid date in the ``datemode==1`` system; its "serial number" is zero. """ if datemode not in (0, 1): raise XLDateBadDatemode(datemode) if xldate == 0.00: return (0, 0, 0, 0, 0, 0) if xldate < 0.00: raise XLDateNegative(xldate) xldays = int(xldate) frac = xldate - xldays seconds = int(round(frac * 86400.0)) assert 0 <= seconds <= 86400 if seconds == 86400: hour = minute = second = 0 xldays += 1 else: # second = seconds % 60; minutes = seconds // 60 minutes, second = divmod(seconds, 60) # minute = minutes % 60; hour = minutes // 60 hour, minute = divmod(minutes, 60) if xldays >= _XLDAYS_TOO_LARGE[datemode]: raise XLDateTooLarge(xldate) if xldays == 0: return (0, 0, 0, hour, minute, second) if xldays < 61 and datemode == 0: raise XLDateAmbiguous(xldate) jdn = xldays + _JDN_delta[datemode] yreg = ((((jdn * 4 + 274277) // 146097) * 3 // 4) + jdn + 1363) * 4 + 3 mp = ((yreg % 1461) // 4) * 535 + 333 d = ((mp % 16384) // 535) + 1 # mp /= 16384 mp >>= 14 if mp >= 10: return ((yreg // 1461) - 4715, mp - 9, d, hour, minute, second) else: return ((yreg // 1461) - 4716, mp + 3, d, hour, minute, second) def xldate_as_datetime(xldate, datemode): """ Convert an Excel date/time number into a :class:`datetime.datetime` object. :param xldate: The Excel number :param datemode: 0: 1900-based, 1: 1904-based. :returns: A :class:`datetime.datetime` object. """ # Set the epoch based on the 1900/1904 datemode. if datemode: epoch = epoch_1904 else: if xldate < 60: epoch = epoch_1900 else: # Workaround Excel 1900 leap year bug by adjusting the epoch. epoch = epoch_1900_minus_1 # The integer part of the Excel date stores the number of days since # the epoch and the fractional part stores the percentage of the day. days = int(xldate) fraction = xldate - days # Get the the integer and decimal seconds in Excel's millisecond resolution. seconds = int(round(fraction * 86400000.0)) seconds, milliseconds = divmod(seconds, 1000) return epoch + datetime.timedelta(days, seconds, 0, milliseconds) # === conversions from date/time to xl numbers def _leap(y): if y % 4: return 0 if y % 100: return 1 if y % 400: return 0 return 1 _days_in_month = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) def xldate_from_date_tuple(date_tuple, datemode): """ Convert a date tuple (year, month, day) to an Excel date. :param year: Gregorian year. :param month: ``1 <= month <= 12`` :param day: ``1 <= day <= last day of that (year, month)`` :param datemode: 0: 1900-based, 1: 1904-based. :raises xlrd.xldate.XLDateAmbiguous: :raises xlrd.xldate.XLDateBadDatemode: :raises xlrd.xldate.XLDateBadTuple: ``(year, month, day)`` is too early/late or has invalid component(s) :raises xlrd.xldate.XLDateError: """ year, month, day = date_tuple if datemode not in (0, 1): raise XLDateBadDatemode(datemode) if year == 0 and month == 0 and day == 0: return 0.00 if not (1900 <= year <= 9999): raise XLDateBadTuple("Invalid year: %r" % ((year, month, day),)) if not (1 <= month <= 12): raise XLDateBadTuple("Invalid month: %r" % ((year, month, day),)) if (day < 1 or (day > _days_in_month[month] and not(day == 29 and month == 2 and _leap(year)))): raise XLDateBadTuple("Invalid day: %r" % ((year, month, day),)) Yp = year + 4716 M = month if M <= 2: Yp = Yp - 1 Mp = M + 9 else: Mp = M - 3 jdn = (1461 * Yp // 4) + ((979 * Mp + 16) // 32) + \ day - 1364 - (((Yp + 184) // 100) * 3 // 4) xldays = jdn - _JDN_delta[datemode] if xldays <= 0: raise XLDateBadTuple("Invalid (year, month, day): %r" % ((year, month, day),)) if xldays < 61 and datemode == 0: raise XLDateAmbiguous("Before 1900-03-01: %r" % ((year, month, day),)) return float(xldays) def xldate_from_time_tuple(time_tuple): """ Convert a time tuple ``(hour, minute, second)`` to an Excel "date" value (fraction of a day). :param hour: ``0 <= hour < 24`` :param minute: ``0 <= minute < 60`` :param second: ``0 <= second < 60`` :raises xlrd.xldate.XLDateBadTuple: Out-of-range hour, minute, or second """ hour, minute, second = time_tuple if 0 <= hour < 24 and 0 <= minute < 60 and 0 <= second < 60: return ((second / 60.0 + minute) / 60.0 + hour) / 24.0 raise XLDateBadTuple("Invalid (hour, minute, second): %r" % ((hour, minute, second),)) def xldate_from_datetime_tuple(datetime_tuple, datemode): """ Convert a datetime tuple ``(year, month, day, hour, minute, second)`` to an Excel date value. For more details, refer to other xldate_from_*_tuple functions. :param datetime_tuple: ``(year, month, day, hour, minute, second)`` :param datemode: 0: 1900-based, 1: 1904-based. """ return ( xldate_from_date_tuple(datetime_tuple[:3], datemode) + xldate_from_time_tuple(datetime_tuple[3:]) )