diff --git a/.carthorse.yml b/.carthorse.yml new file mode 100644 index 00000000..7b6ca85b --- /dev/null +++ b/.carthorse.yml @@ -0,0 +1,9 @@ +carthorse: + 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 diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..73edaec3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,98 @@ +version: 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 diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..3fb98a12 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,10 @@ +[run] +source = xlrd,scripts,tests + +[report] +exclude_lines = + # the original exclude + pragma: no cover + + # debug stuff + if DEBUG: diff --git a/.gitignore b/.gitignore index c45445b8..8e46f60d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ /build /dist -*.egg-info \ No newline at end of file +*.egg-info +build/ +_build/ +*.pyc +/.coverage +/.tox +/*.xml +/htmlcov +MANIFEST +/bin +.Python +/include +/lib diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..4bc268f0 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,10 @@ +version: 2 +python: + version: 3.8 + install: + - method: pip + path: . + extra_requirements: + - docs +sphinx: + fail_on_warning: true diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 00000000..24adbbc2 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,590 @@ +Changes +======= + +2.0.2 (14 June 2025) +-------------------- + +- Fix bug reading sheets containing invalid formulae. + +Thanks to sanshi42 for the fix! + +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 diff --git a/HISTORY.html b/HISTORY.html deleted file mode 100644 index 32f26352..00000000 --- a/HISTORY.html +++ /dev/null @@ -1,339 +0,0 @@ -TODO -- refer to svn log if you really need to know :-) - -

Revision : 3782 -- Author: sjmachin -- Date: 2009-02-23 23:00:50 -

-

Revision : 3613 -- Author: chris -- Date: 2008-11-22 04:06:36 -

-

Revision : 3574 -- Author: sjmachin -- Date: 2008-11-04 11:51:20 -

-

Revision : 3480 -- Author: chris -- Date: 2008-09-19 20:43:00 -

-

Revision : 3431 -- Author: sjmachin -- Date: 2008-07-28 10:37:35 -

-

Revision : 3311 -- Author: chris -- Date: 2008-03-14 22:09:01 -

-

Revision : 3287 -- Author: sjmachin -- Date: 2008-02-14 06:33:32 -

-

Revision : 3284 -- Author: sjmachin -- Date: 2008-02-09 05:37:57 -

-

Revision : 3265 -- Author: sjmachin -- Date: 2007-12-25 19:09:45 -

-

Revision : 3263 -- Author: sjmachin -- Date: 2007-12-20 07:04:55 -

-

Revision : 3262 -- Author: sjmachin -- Date: 2007-12-11 07:40:33 -

-

Revision : 3250 -- Author: sjmachin -- Date: 2007-12-04 20:37:14 -

-

Revision : 3234 -- Author: sjmachin -- Date: 2007-11-21 00:55:56 -

-

Revision : 3168 -- Author: sjmachin -- Date: 2007-10-13 09:19:01 -

-

Revision : 2868 -- Author: sjmachin -- Date: 2007-07-11 11:02:55 -

- -

Version 0.6.1, 2007-06-10 -

- - -

Version 0.6.1a5 -

- - -

Version 0.6.1a4 -

- - -

Version 0.6.1a3 -

- - -

Version 0.6.1a2 -

- - -

Version 0.6.1a1, 2006-12-18 -

- - -

Version 0.6.0a4, not released -

- - -

Version 0.6.0a3, 2006-09-19 -

- - -

Version 0.6.0a2, 2006-09-13 -

- - -

Version 0.6.0a1, 2006-09-08 -

- - -

Version 0.5.3a1, 2006-05-24 -

- - -

Version 0.5.2, 2006-03-14, public release -

- - -

Version 0.5.2a3, 2006-03-13 -

- - -

Version 0.5.2a2, 2006-03-09 -

- - -

Version 0.5.2a1, 2006-03-06 -

- - -

Version 0.5.1, 2006-02-18, released to Journyx -

- - -

Version 0.5, 2006-02-07, released to Journyx -

- - -

Version 0.4a1, 2005-09-07, released to Laurent T. -

- - -

Version 0.3a1, 2005-05-15, first public release -

- diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..54acf3ca --- /dev/null +++ b/LICENSE @@ -0,0 +1,76 @@ +There 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. + */ diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index f03b367a..00000000 --- a/MANIFEST +++ /dev/null @@ -1,19 +0,0 @@ -setup.py -README.html -HISTORY.html -scripts/runxlrd.py -xlrd/__init__.py -xlrd/biffh.py -xlrd/book.py -xlrd/compdoc.py -xlrd/formatting.py -xlrd/formula.py -xlrd/info.py -xlrd/licences.py -xlrd/sheet.py -xlrd/timemachine.py -xlrd/xldate.py -xlrd/doc/compdoc.html -xlrd/doc/xlrd.html -xlrd/examples/namesdemo.xls -xlrd/examples/xlrdnameAPIdemo.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..86dc99ad --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include CHANGELOG.rst +include LICENSE +include README.md diff --git a/README.html b/README.html deleted file mode 100644 index bbc3675f..00000000 --- a/README.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - -The xlrd Module -- README - - - -

Python package "xlrd"

- -

Purpose: Provide a library for developers to use to extract data - from Microsoft Excel (tm) spreadsheet files. - It is not an end-user tool. -

-

Author: John Machin, Lingfo Pty Ltd (sjmachin@lexicon.net) -

-

Licence: BSD-style (see licences.py) -

-

Version of xlrd: 0.7.1 -- 2009-05-31 -

-

Versions of Python supported: 2.6-2.7. -

-

External modules required: -

-
The package itself is pure Python with no dependencies on modules or packages - outside the standard Python distribution. -
-
-

Versions of Excel supported: - 2004, 2003, XP, 2000, 97, 95, 5.0, 4.0, 3.0, 2.1, 2.0. - Support for Excel 2007 .xlsx files scheduled for version 0.7.1. -

-

Outside the current scope: xlrd will safely and reliably ignore any of these -if present in the file: -

-
    -
  • Charts, Macros, Pictures, any other embedded object. WARNING: currently - this includes embedded worksheets. -
  • -
  • VBA modules -
  • -
  • Formulas (results of formula calculations are extracted, of course). -
  • -
  • Comments -
  • -
  • Hyperlinks -
  • -
  • Autofilters, advanced filters, pivot tables, conditional formatting, data validation -
  • -
-

Unlikely to be done: -

-
  • Handling password-protected (encrypted) files. -
  • -
-

Particular emphasis (refer docs for details): -

-
  • Operability across OS, regions, platforms -
  • -
  • Handling Excel's date problems, including the Windows / Macintosh - four-year differential. -
  • -
  • Providing access to named constants and named groups of cells (from version 0.6.0) -
  • -
  • Providing access to "visual" information: font, "number format", background, border, - alignment and protection for cells, height/width etc for rows/columns (from version 0.6.1) -
  • -
-

Quick start: -

-
    import xlrd
-    book = xlrd.open_workbook("myfile.xls")
-    print "The number of worksheets is", book.nsheets
-    print "Worksheet name(s):", book.sheet_names()
-    sh = book.sheet_by_index(0)
-    print sh.name, sh.nrows, sh.ncols
-    print "Cell D30 is", sh.cell_value(rowx=29, colx=3)
-    for rx in range(sh.nrows):
-        print sh.row(rx)
-    # Refer to docs for more details.
-    # Feedback on API is welcomed.
-

-

-

Another quick start: This will show the first, second and last rows of each - sheet in each file: -

- -
    OS-prompt>python PYDIR/scripts/runxlrd.py 3rows *blah*.xls
- -

Installation: -

-
  • On Windows: use the installer. -
  • -
  • Any OS: Unzip the .zip file into a suitable directory, - chdir to that directory, then do "python setup.py install". -
  • -
  • If PYDIR is your Python installation directory: - the main files are in PYDIR/Lib/site-packages/xlrd - the docs are in the doc subdirectory, - and there's a sample script: PYDIR/Scripts/runxlrd.py -
  • -
  • If os.sep != "/": make the appropriate adjustments. -
  • -
-

Download URLs: -

-
  • http://pypi.python.org/pypi/xlrd -
  • -
  • http://www.lexicon.net/sjmachin/xlrd.htm -
  • -
-

Acknowledgements: -

-
  • 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 <david@giffin.org>." -
  • -
  • 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/) -
  • -
  • << a growing list of names; see HISTORY.html >>: feedback, testing, test files, ... -
- - - diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..e3b8077b --- /dev/null +++ b/README.rst @@ -0,0 +1,60 @@ +xlrd +==== + +|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:: bash + + pip install xlrd + +.. 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 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..a4635183 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,135 @@ +# 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." diff --git a/docs/acknowledgements.rst b/docs/acknowledgements.rst new file mode 100644 index 00000000..62530f4d --- /dev/null +++ b/docs/acknowledgements.rst @@ -0,0 +1,32 @@ +Acknowledgements +================ + +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/ diff --git a/docs/api.rst b/docs/api.rst new file mode 100644 index 00000000..7e4f643b --- /dev/null +++ b/docs/api.rst @@ -0,0 +1,67 @@ +API 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: diff --git a/docs/changes.rst b/docs/changes.rst new file mode 100644 index 00000000..d7124209 --- /dev/null +++ b/docs/changes.rst @@ -0,0 +1,4 @@ + +.. currentmodule:: xlrd + +.. include:: ../CHANGELOG.rst diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..2e786c11 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,29 @@ +import 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-2019 Stephen John Machin, Lingfo Pty Ltd. ' + '2019-%s Chris Withers' +) % 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' diff --git a/docs/dates.rst b/docs/dates.rst new file mode 100644 index 00000000..84e18cb6 --- /dev/null +++ b/docs/dates.rst @@ -0,0 +1,87 @@ +Dates 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`. diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 00000000..cf298489 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,52 @@ +Development +=========== + +.. 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. diff --git a/docs/formatting.rst b/docs/formatting.rst new file mode 100644 index 00000000..41e3e8c4 --- /dev/null +++ b/docs/formatting.rst @@ -0,0 +1,124 @@ +Formatting 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. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..06b774cd --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,35 @@ +.. 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` diff --git a/docs/licenses.rst b/docs/licenses.rst new file mode 100644 index 00000000..ef8abac3 --- /dev/null +++ b/docs/licenses.rst @@ -0,0 +1,4 @@ +Licenses +======== + +.. literalinclude:: ../LICENSE diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..294159fb --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,178 @@ +@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 diff --git a/docs/on_demand.rst b/docs/on_demand.rst new file mode 100644 index 00000000..5d74b882 --- /dev/null +++ b/docs/on_demand.rst @@ -0,0 +1,57 @@ +Loading 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. diff --git a/docs/references.rst b/docs/references.rst new file mode 100644 index 00000000..221c8602 --- /dev/null +++ b/docs/references.rst @@ -0,0 +1,38 @@ +Named 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``). diff --git a/docs/unicode.rst b/docs/unicode.rst new file mode 100644 index 00000000..80574c82 --- /dev/null +++ b/docs/unicode.rst @@ -0,0 +1,37 @@ +Handling 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 diff --git a/make_doc.bat b/make_doc.bat deleted file mode 100644 index 357b26ab..00000000 --- a/make_doc.bat +++ /dev/null @@ -1,6 +0,0 @@ -c:\python25\python pkg_doc.py xlrd -c:\python25\python pythondoc.py xlrd/compdoc.py -del compdoc.html -rename pythondoc-compdoc.html compdoc.html -copy xlrd.html xlrd\doc -copy compdoc.html xlrd\doc diff --git a/pkg_doc.py b/pkg_doc.py deleted file mode 100644 index 3e00b417..00000000 --- a/pkg_doc.py +++ /dev/null @@ -1,31 +0,0 @@ -from pythondoc import ET, parse, CompactHTML -import sys - -MODULE_NAME = "xlrd" -PATH_TO_FILES = sys.argv[1] - - -module = ET.Element("module", name=MODULE_NAME) - -parts = [ - '__init__', - 'sheet', - 'xldate', - # 'compdoc', - 'biffh', - 'formatting', - 'formula', - ] -flist = ["%s/%s.py" % (PATH_TO_FILES, p) for p in parts] -for fname in flist: - print "about to parse", fname - elem = parse(fname) - for elem in elem: - if module and elem.tag == "info": - # skip all module info sections except the first - continue - module.append(elem) - -formatter = CompactHTML() -print formatter.save(module, MODULE_NAME), "ok" - diff --git a/pythondoc.py b/pythondoc.py deleted file mode 100644 index 2378019a..00000000 --- a/pythondoc.py +++ /dev/null @@ -1,1429 +0,0 @@ -# -#!/usr/bin/env python -# -*- coding: ascii -*- -# -# $Id: pythondoc.py 3271 2007-09-09 09:45:14Z fredrik $ -# pythondoc documentation generator -# -# history: -# 2003-10-19 fl first preview release (2.0a1) -# 2003-10-19 fl fix HTML in descriptor tags, 1.5.2 tweaks, etc (2.0a2) -# 2003-10-20 fl added encoding support, default HTML generator, etc (2.0a3) -# 2003-10-21 fl fixed some 1.5.2 issues, etc (2.0b1) -# 2003-10-22 fl HTML tweaks, pluggable output generators, etc (2.0b2) -# 2003-10-23 fl fixed encoding, added @author, @version, @since etc -# 2003-10-24 fl disable XML output by default -# 2003-10-25 fl moved info properties into an 'info' element -# 2003-10-26 fl expand wildcards on windows (2.0b3) -# 2003-10-30 fl added support for RISC OS -# 2003-10-31 fl (experimental) support module-level comments -# 2003-11-01 fl minor HTML tweaks (2.0b4) -# 2003-11-03 fl pythondoc 2.0 final -# 2003-11-15 fl added support for inline @link/@linkplain tags (2.1b1) -# 2003-11-20 fl fixed class attribute parsing bug -# 2004-03-27 fl handle multiple single-line methods -# 2004-09-01 fl support Python 2.4 decorators (2.1b2) -# 2004-09-21 fl fixed output filename for "pythondoc ." -# 2005-03-25 fl added docstring extraction for classes and methods (2.1b3) -# 2005-06-18 fl fixed correct HTML output when using ElementTree 1.3 (2.1b4) -# 2005-12-23 fl use xml.etree where available -# 2006-04-04 fl refactored comment parser code; added -s support (2.1b5) -# 2006-04-06 fl handle multiple params in docstrings correctly (2.1b6) -# 2007-09-09 fl moved HTML parser into pythondoc module itself -# -# Copyright (c) 2002-2007 by Fredrik Lundh. -# - -## -# This is the PythonDoc tool. This tool parses Python source files -# and generates API descriptions in XML and HTML. -#

-# For more information on the PythonDoc tool and the markup format, see -# the PythonDoc page -# at effbot.org. -## - -# -------------------------------------------------------------------- -# Software License -# -------------------------------------------------------------------- -# -# Copyright (c) 2002-2007 by Fredrik Lundh -# -# By obtaining, using, and/or copying this software and/or its -# associated documentation, you agree that you have read, understood, -# and will comply with the following terms and conditions: -# -# Permission to use, copy, modify, and distribute this software and -# its associated documentation for any purpose and without fee is -# hereby granted, provided that the above copyright notice appears in -# all copies, and that both that copyright notice and this permission -# notice appear in supporting documentation, and that the name of -# Secret Labs AB or the author not be used in advertising or publicity -# pertaining to distribution of the software without specific, written -# prior permission. -# -# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD -# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- -# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR -# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY -# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, -# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS -# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -# OF THIS SOFTWARE. -# -# -------------------------------------------------------------------- - -# to do in later releases: -# -# TODO: test this release under 1.5.2 ! -# TODO: better rendering of constructors/package modules -# TODO: check @param names against @def/define tags -# TODO: support recursive parsing (-R) -# TODO: warn for tags that doesn't make sense for a given target type -# TODO: HTML output localization (the %s module, returns, raises, etc) -# TODO: make compactHTML generate an element tree instead of raw HTML -# -# nice to have, maybe: -# -# IDEA: support multiple output handlers (multiple -O statements); -# make -x an alias for -Oxml -# IDEA: make pythondoc self-contained (include stub element implementation) - -VERSION_DATE = "2.1b7-20070909" -VERSION = VERSION_DATE.split("-")[0] - -COPYRIGHT = "(c) 2002-2007 by Fredrik Lundh" - -# explicitly import site (for exemaker etc) -import site - -# stuff we use in this module -import glob, os, re, string, sys, tokenize - -# make sure elementtree is available -try: - try: - import xml.etree.ElementTree as ET - except ImportError: - import elementtree.ElementTree as ET -except ImportError: - raise RuntimeError( - "PythonDoc %s requires ElementTree 1.1 or later " - "(available from http://effbot.org/downloads)." % VERSION - ) - -# extension separator (not all systems use a period) -try: - EXTSEP = os.extsep -except AttributeError: - EXTSEP = "." - -## -# Debug level. The higher the value, the more junk you'll see on -# standard output. -#

-# You can use the -V option to pythondoc to increase -# the debug level. - -DEBUG = 0 - -## -# Whitespace tokens. These are ignored when the parser is scanning -# for a subject. - -WHITESPACE_TOKEN = ( - tokenize.NL, tokenize.NEWLINE, tokenize.DEDENT, tokenize.INDENT - ) - -## -# Default encoding. To override this for a module, put a "coding" -# directive in your Python module (see PEP 263 for details). - -ENCODING = "iso-8859-1" - -## -# Known tags. The parser generates warnings for tags that are not in -# this list, but it still copies them to the XML infoset. - -TAGS = ( - "def", "defreturn", - "param", "keyparam", - "return", - "throws", "exception", - # javadoc tags not used by the standard generator - "author", "deprecated", "see", "since", "version" - ) - -## -# (Helper) Combines filename prefix with extension part. -# -# @param prefix Filename prefix. -# @param ext Extension string, including a leading period. The -# period is replaced with a platform-specific separator, if -# necessary. -# @return The combined name. - -def joinext(prefix, ext): - assert ext[0] == "." # require leading separator, to match os.path.splitext - return prefix + EXTSEP + ext[1:] - -## -# (Helper) Extracts block tags from a PythonDoc comment. -# -# @param comment Comment text. -# @return A list of (lineno, tag, text) tuples, where the tag is None -# for the initial description. -# @defreturn List of tuples. - -def gettags(comment): - - tags = [] - - tag = None - tag_lineno = lineno = 0 - tag_text = [] - - for line in comment: - if line[:1] == "@": - tags.append((tag_lineno, tag, string.join(tag_text, "\n"))) - line = string.split(line, " ", 1) - tag = line[0][1:] - if len(line) > 1: - tag_text = [line[1]] - else: - tag_text = [] - tag_lineno = lineno - else: - tag_text.append(line) - lineno = lineno + 1 - - tags.append((tag_lineno, tag, string.join(tag_text, "\n"))) - - return tags - -## -# (Helper) Flattens an element tree, returning only the text contents. -# -# @param elem An element tree. -# @return A text string. -# @defreturn String. - -def flatten(elem): - text = elem.text or "" - for e in elem: - text += flatten(e) - if e.tail: - text += e.tail - return text - -## -# (Helper) Extracts summary from a PythonDoc comment. This function -# gets the first complete sentence from the description string. -# -# @param description An element containing the description. -# @return A summary string. -# @defreturn String. - -def getsummary(description): - - description = flatten(description) - - # extract the first sentence from the description - m = re.search("(?s)(.+?\.)\s", description + " ") - if m: - return m.group(1) - - return description # sorry - -## -# (Helper) Parses HTML descriptor text into an XHTML structure. -# -# @param parser Parser instance (provides a warning method). -# @param text Text fragment. -# @return An element tree containing XHTML data. -# @defreturn Element. - -def parsehtml(parser, tag, text, lineno): - - # transcode - if parser.encoding != "ascii": - try: - text = unicode(text, parser.encoding) - except NameError: - pass # 1.5.2 - - # process inline links (@link, @linkplain) - # note that links are replaced with %s" % (href, text) - else: - return "%s" % (href, text) - text = re.sub("\{(@link[^}]+)\}", fixlink, text) - - if "<" not in text and "&" not in text: - # plain text - elem = ET.Element(tag) - elem.text = string.strip(text) - return elem - - p = HTMLTreeBuilder() - ix = 0 - try: - p.feed("<%s>" % tag) - p.feed("

") # make sure everything's wrapped in a paragraph tag - # feed line by line - for line in string.split(text, "\n"): - p.feed(line + "\n") - ix = ix + 1 - p.feed("" % tag) - tree = p.close() - except: - parser.warning( - (lineno+ix, 0), - "HTML parser error near this line (%s)", - sys.exc_value - ) - return ET.Element("p") - - return tree - -# -------------------------------------------------------------------- -# copied from ElementTree/HTMLTreeBuilder.py - -import htmlentitydefs - -AUTOCLOSE = "p", "li", "tr", "th", "td", "head", "body" -IGNOREEND = "img", "hr", "meta", "link", "br" - -if sys.version[:3] == "1.5": - is_not_ascii = re.compile(r"[\x80-\xff]").search # 1.5.2 -else: - is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search - -try: - from HTMLParser import HTMLParser -except ImportError: - from sgmllib import SGMLParser - # hack to use sgmllib's SGMLParser to emulate 2.2's HTMLParser - class HTMLParser(SGMLParser): - # the following only works as long as this class doesn't - # provide any do, start, or end handlers - def unknown_starttag(self, tag, attrs): - self.handle_starttag(tag, attrs) - def unknown_endtag(self, tag): - self.handle_endtag(tag) - -## -# ElementTree builder for HTML source code. This builder converts an -# HTML document or fragment to an ElementTree. -#

-# The parser is relatively picky, and requires balanced tags for most -# elements. However, elements belonging to the following group are -# automatically closed: P, LI, TR, TH, and TD. In addition, the -# parser automatically inserts end tags immediately after the start -# tag, and ignores any end tags for the following group: IMG, HR, -# META, and LINK. -# -# @keyparam builder Optional builder object. If omitted, the parser -# uses the standard elementtree builder. -# @keyparam encoding Optional character encoding, if known. If omitted, -# the parser looks for META tags inside the document. If no tags -# are found, the parser defaults to ISO-8859-1. Note that if your -# document uses a non-ASCII compatible encoding, you must decode -# the document before parsing. - -class HTMLTreeBuilder(HTMLParser): - - def __init__(self, encoding=None): - self.__stack = [] - self.__builder = ET.TreeBuilder() - self.encoding = encoding or "iso-8859-1" - HTMLParser.__init__(self) - - ## - # Flushes parser buffers, and return the root element. - # - # @return An Element instance. - - def close(self): - HTMLParser.close(self) - return self.__builder.close() - - ## - # (Internal) Handles start tags. - - def handle_starttag(self, tag, attrs): - if tag == "meta": - # look for encoding directives - http_equiv = content = None - for k, v in attrs: - if k == "http-equiv": - http_equiv = string.lower(v) - elif k == "content": - content = v - if http_equiv == "content-type" and content: - # use mimetools to parse the http header - import mimetools, StringIO - header = mimetools.Message( - StringIO.StringIO("%s: %s\n\n" % (http_equiv, content)) - ) - encoding = header.getparam("charset") - if encoding: - self.encoding = encoding - if tag in AUTOCLOSE: - if self.__stack and self.__stack[-1] == tag: - self.handle_endtag(tag) - self.__stack.append(tag) - attrib = {} - if attrs: - for k, v in attrs: - attrib[string.lower(k)] = v - self.__builder.start(tag, attrib) - if tag in IGNOREEND: - self.__stack.pop() - self.__builder.end(tag) - - ## - # (Internal) Handles end tags. - - def handle_endtag(self, tag): - if tag in IGNOREEND: - return - lasttag = self.__stack.pop() - if tag != lasttag and lasttag in AUTOCLOSE: - self.handle_endtag(lasttag) - self.__builder.end(tag) - - ## - # (Internal) Handles character references. - - def handle_charref(self, char): - if char[:1] == "x": - char = int(char[1:], 16) - else: - char = int(char) - if 0 <= char < 128: - self.__builder.data(chr(char)) - else: - self.__builder.data(unichr(char)) - - ## - # (Internal) Handles entity references. - - def handle_entityref(self, name): - entity = htmlentitydefs.entitydefs.get(name) - if entity: - if len(entity) == 1: - entity = ord(entity) - else: - entity = int(entity[2:-1]) - if 0 <= entity < 128: - self.__builder.data(chr(entity)) - else: - self.__builder.data(unichr(entity)) - else: - self.unknown_entityref(name) - - ## - # (Internal) Handles character data. - - def handle_data(self, data): - if isinstance(data, type('')) and is_not_ascii(data): - # convert to unicode, but only if necessary - data = unicode(data, self.encoding, "ignore") - self.__builder.data(data) - - ## - # (Hook) Handles unknown entity references. The default action - # is to ignore unknown entities. - - def unknown_entityref(self, name): - pass # ignore by default; override if necessary - -# -------------------------------------------------------------------- - -## -# (Helper) Parses a PythonDoc comment into an PythonDoc info structure. -# -# @param parser Parser instance (provides a warning method). -# @param lineno Line number where this comment starts. -# @param comment A list of text line making up the comment. -# @param dedent If true, strip leading whitespace from all comment -# lines except the first one. -# @return An element tree containing XHTML data. -# @defreturn Element. - -def parsecomment(parser, lineno, comment, dedent=0): - - subject_info = ET.Element("info") - - # untabify - for ix in range(len(comment)): - comment[ix] = string.expandtabs(comment[ix]) - - if dedent: - margin = None - for ix in range(1, len(comment)): - s = string.lstrip(comment[ix]) - if not s: - continue - m = len(comment[ix]) - len(s) - if margin is None: - margin = m - else: - margin = min(m, margin) - if margin: - for ix in range(1, len(comment)): - comment[ix] = comment[ix][margin:] - - for ix, tag, text in gettags(comment): - - pos = lineno + ix + 1, 0 - - # check tag name - if tag is None: - tag = "description" - else: - if tag not in TAGS: - parser.warning( - pos, - "unknown tag in description: @%s", tag - ) - if tag in ("throws", "exception"): - tag = "exception" # PythonDoc extension - - # deal with "named" tags - if tag in ("param", "keyparam", "exception"): - text = string.split(text, " ", 1) - name = text[0] - if len(text) > 1: - text = string.lstrip(text[1]) - else: - text = "" - else: - name = None - - tag_elem = parsehtml(parser, tag, text, pos[0]) - - # generate summaries - if tag == "description": - summary = getsummary(tag_elem) - if summary: - elem = ET.SubElement(subject_info, "summary") - elem.text = summary - - subject_info.append(tag_elem) - - if name: - tag_elem.set("name", name) - - return subject_info - -## -# Module parser. -#

-# This class implements the PythonDoc source code scanner. It reads -# source code from a file or a file-like object, and builds an element -# tree with information about the module. -#

-# Note that the constructor only sets things up for parsing. Use the -# {@link ModuleParser.parse} method to parse the file. Or for -# convenience, use the {@link parse} function to create a parser -# object and parse a given file. -# -# @param file Name of the module source file, or a file object. If a -# file object is used, it must provide a name attribute and -# a readline method. -# @param prefix Optional name prefix. If given, this is prepended to -# the module name. For example, if the prefix is set to "prefix" -# and the module filename is "name.py", the module is assumed to -# contain the "prefix.name" namespace. - -class ModuleParser: - - ## - # Module name. - - name = None - - def __init__(self, file, prefix=None): - if hasattr(file, "readline"): - self.file = file - self.filename = file.name - else: - self.file = None - self.filename = file - name = os.path.splitext(os.path.basename(self.filename))[0] - if prefix and prefix != ".": - name = prefix + "." + name - self.name = name - self.stack = [ - ET.Element( - "module", - name=name, filename=self.filename - ) - ] - self.indent = 0 - self.scope = [] # list of (indent, tag, name, ...) tuples - self.handler = self.look_for_encoding - self.encoding = ENCODING - - ## - # Parses the file. - # - # @keyparam docstring If true, look for markup in docstrings. - # @return An element tree containing information about the module. - # @defreturn Element. - # @exception IOError If the file could not be opened. - - def parse(self, docstring=0): - if self.file is None: - file = open(self.filename) - else: - file = self.file - try: - tokenize.tokenize(file.readline, self.handle_token) - except tokenize.TokenError, v: - message, lineno = v - self.warning(lineno, "exception in tokenizer: %s", message) - if len(self.stack) != 1: - pass # FIXME: print warning? - tree = self.stack[0] # may be incomplete - # fixup internal links - # 1) find all named elements - elems = {} - for elem in tree.getiterator(): - name = elem.get("name") - if name: - elems[name] = elem - # 2) find all link anchors - for elem in tree.getiterator("a"): - href = elem.get("href") - if href[:5] == "link:": - # FIXME: add support for external links - href = href[5:] - if href[:1] == "#": - href = href[1:] - target = elems.get(self.name + "." + href) - if target: - href = "#" + target.get("name") + "-" + target.tag - elem.set("href", href) - if docstring: - # look for markup in docstrings - for info in tree.getiterator("info"): - docstring = info.findtext("docstring") - if not docstring: - continue - comment = docstring.split("\n") - newinfo = parsecomment(self, 0, comment, dedent=1) - for elem in info: - if newinfo.find(elem.tag) is None: - newinfo.append(elem) - info[:] = newinfo - return tree - - ## - # Prints a warning message to standard output. - # - # @param position A (line, column) tuple. The column can be set - # to None if not known (or not relevant). - # @param format Message or format string. - # @param *args Optional arguments. - - def warning(self, position, format, *args): - line, column = position - message = "%s:%d: WARNING: %s" % (self.filename, line, format % args) - sys.stderr.write(message) - sys.stderr.write("\n") - - ## - # Dispatches tokens to the current handler. Each handler should - # return the handler to call for the next token. - #

- # This method also handles indentation and dedentation tokens, - # and manages the scope stack. - - def handle_token(self, *args): - # dispatch incoming tokens to the current handler - if DEBUG > 1: - print self.handler.im_func.func_name, self.indent, - print tokenize.tok_name[args[0]], repr(args[1]) - if args[0] == tokenize.DEDENT: - self.indent = self.indent - 1 - while self.scope and self.scope[-1][0] >= self.indent: - del self.scope[-1] - del self.stack[-1] - self.handler = apply(self.handler, args) - if args[0] == tokenize.INDENT: - self.indent = self.indent + 1 - - ## - # (Token handler) Scans for encoding directive. - - def look_for_encoding(self, type, token, start, end, line): - if type == tokenize.COMMENT: - if string.rstrip(token) == "##": - return self.look_for_pythondoc(type, token, start, end, line) - m = re.search("coding[:=]\s*([-_.\w]+)", token) - if m: - self.encoding = m.group(1) - return self.look_for_pythondoc - if start[0] > 2: - return self.look_for_pythondoc - return self.look_for_encoding - - ## - # (Token handler) Scans for PythonDoc comments. - - def look_for_pythondoc(self, type, token, start, end, line): - if type == tokenize.COMMENT and string.rstrip(token) == "##": - # found a comment: set things up for comment processing - self.comment_start = start - self.comment = [] - return self.process_comment_body - else: - # deal with "bare" subjects - if token == "def" or token == "class": - self.subject_indent = self.indent - self.subject_parens = 0 - self.subject_start = self.comment_start = None - self.subject = [] - return self.process_subject(type, token, start, end, line) - return self.look_for_pythondoc - - ## - # (Token handler) Processes a comment body. This handler adds - # comment lines to the current comment. - - def process_comment_body(self, type, token, start, end, line): - if type == tokenize.COMMENT: - if start[1] != self.comment_start[1]: - self.warning( - start, - "comment line should be aligned with marker" - ) - line = string.rstrip(token) - if line == "##": - # handle module comments (experimental) - # FIXME: add more consistency checks? - if self.stack[0].find("info") is not None: - self.warning( - self.comment_start, - "multiple module comments are not allowed" - ) - # FIXME: ignore additional comments? - self.process_subject_info(None, self.stack[0]) - return self.look_for_pythondoc - elif line[:2] == "# ": - line = line[2:] - elif line[:1] == "#": - line = line[1:] - self.comment.append(line) - else: - if not self.comment: - self.warning( - self.comment_start, - "found pythondoc marker but no comment body" - ) - return self.look_for_pythondoc - self.subject_start = None - self.subject = [] - if type != tokenize.NL: - return self.process_subject(type, token, start, end, line) - return self.process_subject # end of comment - return self.process_comment_body - - ## - # (Token handler) Processes the comment subject. The subject can - # be either a plain variable, or a function/method or class - # definition. - #

- # This method is also used to process "bare" subjects; that is, - # functions, methods, and classes that don't have PythonDoc - # markup. In that case, the comment_start variable is set to - # None. - - def process_subject(self, type, token, start, end, line): - # got an item; deal with it - if self.subject: - # method/function/class definition - definition = self.subject[0] in ("def", "class") - if definition: - if type not in WHITESPACE_TOKEN: - if token == "(": - self.subject_parens = self.subject_parens + 1 - elif token == ")": - self.subject_parens = self.subject_parens - 1 - if self.subject_parens or token != ":": - self.subject.append(token) - return self.process_subject - else: - # simple assignment - if token != "=": - self.warning( - self.subject_start, - "bad subject %s; ignoring description", - repr(self.subject[0]) - ) - # might be a pythondoc marker; pass it to the scanner - return self.look_for_pythondoc( - type, token, start, end, line - ) - # FIXME: keep adding stuff until end of expression - else: - if type in WHITESPACE_TOKEN: - return self.process_subject - if type == tokenize.COMMENT: - self.warning( - start, - "comment between description and subject; " + - "ignoring description" - ) - # might be a pythondoc marker; pass it to the scanner - return self.look_for_pythondoc( - type, token, start, end, line - ) - # FIXME: check token type! - # the @ token type is currently tokenize.ERRORTOKEN; hopefully - # this will change before 2.4 final - if token == "@": - self.decorator_parens = 0 - return self.skip_decorator - self.subject_start = start - self.subject.append(token) - if token in ("def", "class"): - # handle single-line subjects - while self.scope and self.scope[-1][0] >= self.indent: - self.scope.pop() - self.stack.pop() - self.subject_indent = self.indent - self.subject_parens = 0 - return self.process_subject - - # check if this is a method or a function - method = self.scope and self.scope[-1][1] == "class" - - # calculate fully qualified subject name - name = [self.name] - for s in self.scope: - name.append(s[2]) - if definition: - name.append(self.subject[1]) - else: - name.append(self.subject[0]) - - # calculate subject definition statement - statement = [] - for part in self.subject: - if part in ("class", "def"): - continue - statement.append(part) - if part == ",": - statement.append(" ") - if self.subject[0] == "def" and method: - # ignore the first argument for methods - # 'name', '(', 'self', ',', ' ', ...) - del statement[2:min(5, len(statement)-1)] - statement = string.join(statement, "") - - # create subject element - if self.subject[0] == "class": - subject_elem = ET.Element("class") - elif self.subject[0] == "def": - if method: - subject_elem = ET.Element("method") - else: - subject_elem = ET.Element("function") - else: - subject_elem = ET.Element("variable") - - self.stack[-1].append(subject_elem) - - # add new subject to the scope and element stacks - if definition: - self.scope.append((self.subject_indent,) + tuple(self.subject)) - self.stack.append(subject_elem) - - subject_info = self.process_subject_info(name, subject_elem) - - # add local name to info - elem = ET.Element("name") - elem.text = name[-1] - - subject_info.insert(0, elem) - - name = string.join(name, ".") - - subject_elem.set("name", name) - subject_elem.set("lineno", str(self.subject_start[0])) - - if subject_info.find("def") is None and statement: - # add subject definition (unless specified in comment) - elem = ET.Element("def") - elem.text = statement - # add to front, to make the XML easier to read - subject_info.insert(0, elem) - - if definition: - return self.look_for_docstring(type, token, start, end, line) - else: - return self.look_for_pythondoc(type, token, start, end, line) - - ## - # (Token handler) Skips a decorator. - - def skip_decorator(self, type, token, start, end, line): - if token == "(": - self.decorator_parens = self.decorator_parens + 1 - elif token == ")": - self.decorator_parens = self.decorator_parens - 1 - if self.decorator_parens or type != tokenize.NEWLINE: - return self.skip_decorator - return self.process_subject - - ## - # (Token handler helper) Processes a PythonDoc comment. This - # method creates an "info" element based on the current comment, - # and attaches it to the current subject element. - # - # @param subject_name Subject name (or None if the name is not known). - # @param subject_elem The current subject element. - # @return The info element. Note that this element has already - # been attached to the subject element. - # @defreturn Element - - def process_subject_info(self, subject_name, subject_elem): - - # process pythondoc comment (if any) - if self.comment_start: - subject_info = parsecomment( - self, self.comment_start[0], self.comment - ) - else: - subject_info = ET.Element("info") - - subject_elem.append(subject_info) - - if DEBUG: - if subject_name: - subject_name = string.join(subject_name, ".") - else: - subject_name = "" - print "---", subject_name - prefix = " " * len(self.scope) - for line in self.comment: - print prefix + line - - return subject_info - - ## - # (Token handler) Look for docstring inside a definition. - - def look_for_docstring(self, type, token, start, end, line): - if type in WHITESPACE_TOKEN or token == ":": - return self.look_for_docstring - if type == tokenize.STRING: - subject_elem = self.stack[-1] - subject_info = subject_elem.find("info") - if subject_info is not None: - elem = ET.SubElement(subject_info, "docstring") - # FIXME: add string sanity check here, before doing eval - elem.text = eval(token) - return self.skip_subject_body(type, token, start, end, line) - - ## - # (Token handler) Skips over the subject body. - - def skip_subject_body(self, type, token, start, end, line): - # for now, just hand control back to the pythondoc scanner, - # and let it skip over the subject body while looking for the - # next marker. - return self.look_for_pythondoc(type, token, start, end, line) - -## -# Parses a module. -#

-# This function creates a {@link #ModuleParser} instance, and uses it -# to parse the given file. For details, see {@linkplain #ModuleParser -# the ModuleParser documentation}. -# -# @param file Name of the module source file, or a file object. -# @param prefix Optional name prefix. -# @keyparam docstring If true, look for markup in docstrings. -# @return An element tree containing the module description. -# @defreturn Element. -# @exception IOError If the file could not be found, or could not -# be opened for reading. - -def parse(file, prefix=None, docstring=0): - m = ModuleParser(file, prefix) - return m.parse(docstring=docstring) - -# -------------------------------------------------------------------- -# default formatter - -if sys.version[:3] == "1.5": - _escape = re.compile(r"[&<>\"\x80-\xff]") # 1.5.2 -else: - _escape = re.compile(eval(r'u"[&<>\"\u0080-\uffff]"')) - -_escape_map = { - "&": "&", - "<": "<", - ">": ">", - '"': """, -} - -## -# Encodes reserved HTML characters and non-ASCII characters as HTML -# character references. -# -# @def html_encode(text) -# @param text Source text. -# @return An encoded string. - -def html_encode(text, pattern=_escape): - if not text: - return "" - def escape_entities(m, map=_escape_map): - char = m.group() - text = map.get(char) - if text is None: - text = "&#%d;" % ord(char) - return text - text = pattern.sub(escape_entities, text) - try: - return text.encode("ascii") - except AttributeError: - return text # 1.5.2 - -## -# Compact HTML formatter. This formatter turns a module XML -# description into a minimal HTML document. -#

-# This formatter supports the following options: -#

-#
-#
-Dstyle=URL
-#
Stylesheet URL. If this option is present, the formatter adds a -# stylesheet <link> element to the HTML output.
-#
-Dzone
-#
Generate effbot.org zone documents.
-#
-# -# @param options Options dictionary. - -class CompactHTML: - - def __init__(self, options=None): - self.options = options or {} - - ## - # Writes an element containing some text (plain or formatted). - # - # @param elem Element. - # @param compact If true, try to minimize the amount of vertical - # padding. - - def writetext(self, elem, compact=0): - if len(elem): - if compact and len(elem) == 1 and elem[0].tag == "p": - elem = elem[0] - self.file.write(html_encode(elem.text)) - for e in elem: - ET.ElementTree(e).write(self.file) - self.file.write(html_encode(elem.tail)) - else: - for e in elem: - ET.ElementTree(e).write(self.file) - elif elem is not None and elem.text: - if compact: - self.file.write(html_encode(elem.text)) - else: - self.file.write("

%s

\n" % html_encode(elem.text)) - - ## - # Writes an object description (the description text, parameters, - # return values) etc. - # - # @param object The object element. - # @param summary If true, use summary instead of full description. - - def writeobject(self, object, summary=0): - name = object.get("name") - info = object.find("info") - localname = string.split(name, ".")[-1] - define = info.findtext("def") - anchor = html_encode(name + "-" + object.tag) - if object.tag == "class": - # look for the constructor - for obj in object: - if obj.get("name") == name + ".__init__": - inf = obj.find("info") - define = string.split(inf.findtext("def"), "(", 1) - define = localname + "(" + define[1] - self.file.write( - "
%s (class)" % html_encode(define) - ) - break - else: - self.file.write( - "
%s (class) " % html_encode(localname) - ) - elif object.tag == "variable": - self.file.write( - "
%s (variable)" % ( - anchor, anchor, html_encode(localname) - ) - ) - else: - self.file.write( - "
%s" % ( - anchor, anchor, html_encode(define) - ) - ) - if object.tag == "function" or object.tag == "method": - defreturn = info.find("defreturn") - if defreturn is not None: - defreturn = flatten(defreturn) - self.file.write(" ⇒ %s" % html_encode(defreturn)) - self.file.write(" [#]
\n" % anchor) - self.file.write("
\n") - if summary: - text = info.findtext("summary") - if text: - self.file.write("

%s

\n" % html_encode(text)) - else: - self.writetext(info.find("description")) - param = info.findall("param") - keyparam = info.findall("keyparam") - exception = info.findall("exception") - return_elem = info.find("return") - if param or keyparam or exception or return_elem != None: - self.file.write("
\n") - for p in param + keyparam: - name = p.get("name") - if p.tag == "keyparam": - name = name + "=" - self.file.write("
%s
\n" % html_encode(name)) - self.file.write("
\n") - self.writetext(p, compact=1) - self.file.write("
\n") - if return_elem is not None: - self.file.write("
Returns:
\n") - self.file.write("
\n") - self.writetext(return_elem, compact=1) - self.file.write("
\n") - for e in exception: - name = html_encode(e.get("name")) - self.file.write("
Raises %s:
" % name) - self.file.write("
\n") - self.writetext(e, compact=1) - self.file.write("
\n") - self.file.write("

\n") - if object.tag == "class" and summary: - self.file.write( - "

For more information about this class, see " - "The %s Class.

\n" % ( - anchor, localname - ) - ) - self.file.write("
\n") - - ## - # Writes a module description to file. - # - # @param module A module element tree, as returned by {@link - # ModuleParser.parse}. - # @param file Output file name (minus extension). - # @return If successful, the output filename used to store the - # module. - # @defreturn String or None. - - def save(self, module, file): - - title = "The %s Module" % module.get("name") - - zone = self.options.has_key("zone") - - if zone: - filename = joinext(file, ".txt") - else: - filename = joinext(file, ".html") - self.file = open(filename, "w") - - if zone: - # generate zone document - self.file.write(title + "\n\n") - else: - self.file.write( - "\n" - ) - self.file.write("\n\n") - self.file.write( - "\n" - ) - self.file.write("%s\n" % html_encode(title)) - try: - style = self.options["style"] - except KeyError: - pass - else: - self.file.write( - "\n" % - html_encode(style) - ) - self.file.write("\n\n") - self.file.write("

%s

\n" % title) - - # 0) module comments - info = module.find("info") - if info is not None: - self.writetext(info.find("description")) - self.file.write("

Module Contents

\n") - - # 1) toplevel subjects (including class overviews) - objects = [] - for object in module: - info = object.find("info") - if info is None or info.find("description") is None: - continue - if object.tag in ("variable", "function", "class"): - objects.append(object) - objects.sort(lambda a, b: cmp( - string.lower(string.split(a.get("name"), ".")[-1]), - string.lower(string.split(b.get("name"), ".")[-1]) - )) - self.file.write("
\n") - for object in objects: - self.writeobject(object, object.tag == "class") - self.file.write("
\n") - # 2) class descriptions - for object in objects: - if object.tag != "class": - continue - name = object.get("name") - localname = string.split(name, ".")[-1] - anchor = name + "-class" - self.file.write( - "

The %s Class

\n" % ( - anchor, anchor, localname - ) - ) - self.file.write("
\n") - self.writeobject(object) - objects = [] - for object in object: - info = object.find("info") - if info is None or info.find("description") is None: - continue - if object.tag not in ("method", "variable"): - continue - objects.append(object) - objects.sort(lambda a, b: cmp( - string.lower(string.split(a.get("name"), ".")[-1]), - string.lower(string.split(b.get("name"), ".")[-1]) - )) - for object in objects: - if object.tag == "variable": - object.tag = "attribute" - self.writeobject(object) - if object.tag == "attribute": - object.tag = "variable" - self.file.write("
\n") - - if not zone: - self.file.write("\n") - - self.file.close() - self.file = None - - return filename - -## -# Prints a usage message and exits. - -def usage(): - print "PythonDoc", VERSION, COPYRIGHT - print - print "Usage:" - print - print " pythondoc [options] files..." - print - print "where the files can be either python modules or package" - print "directories." - print - print "Options:" - print - print " -p prefix Prepend given prefix to symbol names." - print " -f Generate output also for files without descriptions." - print " -x Generate XML output (pythondoc infosets)." - print - print " -s Look for markup in docstrings (experimental)." - print - print "Output options:" - print - print " -O format Use given output format handler." - print " -D name Define output variable." - print " -D name=text Set output variable to given text." - print - print "For more information on PythonDoc and the PythonDoc comment syntax," - print "see http://effbot.org/zone/pythondoc.htm" - sys.exit(1) - -if __name__ == "__main__": - - import getopt - - try: - opts, args = getopt.getopt(sys.argv[1:], "D:fO:p:Vsx") - except getopt.error: - usage() - - force = 0 - prefix = None - docstring = 0 - output_xml = 0 - output_handler = CompactHTML - output_options = {} - - for k, v in opts: - if k == "-f": - force = 1 - elif k == "-p": - prefix = v - elif k == "-s": - docstring = 1 - elif k == "-x": - output_xml = 1 - elif k == "-O": - try: - m = __import__(v) - for k in string.split(v, ".")[1:]: - m = getattr(m, k) - output_handler = getattr(m, "PythonDocGenerator") - except (ImportError, AttributeError): - print "cannot find/load", repr(v), "generator" - sys.exit(1) - elif k == "-D": - try: - k, v = string.split(v, "=", 1) - except ValueError: - k = v; v = None - output_options[k] = v - elif k == "-V": - DEBUG = DEBUG + 1 - - if not args: - usage() - - # instantiate output handler - output_handler = output_handler(output_options) - - # check if handler supports custom tags - try: - TAGS = TAGS + output_handler.tags - except AttributeError: - pass - - import time - t0 = time.time() - - input = output = 0 - - for filename in args: - - this_prefix = prefix - - if os.path.isdir(filename): - # FIXME: explicitly check if this is a package? - files = glob.glob(os.path.join(filename, joinext("*", ".py"))) - if not this_prefix: - this_prefix = os.path.basename(filename) - else: - if sys.platform == "win32" and glob.has_magic(filename): - files = glob.glob(filename) - else: - files = [filename] - - files.sort() - - for file in files: - - try: - module = parse(file, this_prefix, docstring=docstring) - except IOError, v: - sys.stderr.write("%s error: %s\n" % (file, v[1])) - continue - - input = input + 1 - - # check if any toplevel object has a description - if not force: - for n in module: - i = n.find("info") - if i and i.find("description") is not None: - break - else: - continue # no documented subjects - - f = "pythondoc-" + string.replace(module.get("name"), ".", EXTSEP) - - if output_xml: - # generate XML - filename = joinext(f, ".xml") - try: - out = open(filename, "w") - ET.ElementTree(module).write(out) - out.close() - except IOError, v: - sys.stderr.write("%s error: %s\n" % (filename, v[1])) - else: - sys.stderr.write("%s ok\n" % filename) - - # generate output - try: - out = output_handler.save(module, f) - except IOError, v: - sys.stderr.write("%s error: %s\n" % (file, v[1])) - else: - if out: - sys.stderr.write("%s ok\n" % out) - - output = output + 1 - - # flush output handler - try: - done = output_handler.done - except AttributeError: - pass - else: - out = output_handler.done() - if out: - sys.stderr.write("%s ok\n" % out) - - if DEBUG: - sys.stderr.write( - "%d files parsed, %d descriptions generated, in %.2f seconds\n" % ( - input, output, time.time() - t0 - )) diff --git a/scripts/runxlrd.py b/scripts/runxlrd.py index 2bc1343c..b284a594 100644 --- a/scripts/runxlrd.py +++ b/scripts/runxlrd.py @@ -1,8 +1,9 @@ #!/usr/bin/env python -# -*- coding: ascii -*- -#

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.

+# 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: @@ -30,18 +31,15 @@ options = None if __name__ == "__main__": + import xlrd + import sys + import time + import glob + import traceback + import gc - PSYCO = 0 + from xlrd.timemachine import xrange, REPR - import xlrd - import sys, time, glob, traceback, pprint, gc - - try: - object - except NameError: - # Python 2.1 - class object: - pass class LogHandler(object): @@ -49,33 +47,33 @@ 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 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) + 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) + print("cell %s%d: type=%d, data: %r" % (xlrd.colname(colx), rowx+1, ty, val)) def get_row_data(bk, sh, rowx, colrange): result = [] @@ -92,9 +90,8 @@ def get_row_data(bk, sh, rowx, colrange): if cty == xlrd.XL_CELL_DATE: try: showval = xlrd.xldate_as_tuple(cval, dmode) - except xlrd.XLDateError: - e1, e2 = sys.exc_info()[:2] - showval = "%s:%s" % (e1.__name__, e2) + 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) @@ -104,26 +101,26 @@ def get_row_data(bk, sh, rowx, colrange): 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 "Pickleable: %d; Use mmap: %d; Formatting: %d; On demand: %d" \ - % (bk.pickleable, bk.use_mmap, bk.formatting_info, bk.on_demand) - print "Ragged rows: %d" % bk.ragged_rows + 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 + 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:" + print("Fonts:") for x in xrange(len(bk.font_list)): font = bk.font_list[x] font.dump(header='== Index %d ==' % x, indent=4) @@ -131,26 +128,26 @@ def show_fonts(bk): def show_names(bk, dump=0): bk_header(bk) if bk.biff_version < 50: - print "Names not extracted in this BIFF version" + print("Names not extracted in this BIFF version") return nlist = bk.name_obj_list - print "Name list: %d entries" % len(nlist) + 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) + 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)) + 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)) + print(" %s: %r" % (xlrd.cellname(rx, cx), sh.cell_value(rx, cx))) def show_labels(bk): # bk_header(bk) @@ -163,8 +160,8 @@ def show_labels(bk): 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("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) @@ -173,10 +170,10 @@ def show(bk, nshow=65535, printit=1): bk_header(bk) if 0: rclist = xlrd.sheet.rc_stats.items() - rclist.sort() - print "rc stats" + rclist = sorted(rclist) + print("rc stats") for k, v in rclist: - print "0x%04x %7d" % (k, v) + print("0x%04x %7d" % (k, v)) if options.onesheet: try: shx = int(options.onesheet) @@ -185,38 +182,38 @@ def show(bk, nshow=65535, printit=1): shxrange = [shx] else: shxrange = range(bk.nsheets) - # print "shxrange", shxrange + # 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 = %r; nrows = %d; ncols = %d" % \ - (shx, sh.name, sh.nrows, sh.ncols) + 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: - _junk = sh.row_types(rowx)[nc-1] - _junk = sh.row_values(rowx)[nc-1] - _junk = sh.cell(rowx, nc-1) + 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,) + 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 + 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, ncols = sh.nrows, sh.ncols - print "sheet %d: name = %r; nrows = %d; ncols = %d" % \ - (shx, sh.name, sh.nrows, sh.ncols) + 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): @@ -225,14 +222,14 @@ def count_xfs(bk): 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 + 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, PSYCO + global options usage = "\n%prog [options] command [input-file-patterns]\n" + cmd_doc oparser = optparse.OptionParser(usage) oparser.add_option( @@ -243,10 +240,6 @@ def main(cmd_args): "-v", "--verbosity", type="int", default=0, help="level of information and diagnostics provided") - oparser.add_option( - "-p", "--pickleable", - type="int", default=1, - help="1: ensure Book object is pickleable (default); 0: don't bother") oparser.add_option( "-m", "--mmap", type="int", default=-1, @@ -259,8 +252,8 @@ def main(cmd_args): "-f", "--formatting", type="int", default=0, help="0 (default): no fmt info\n" - "1: fmt info (all cells)\n" - ) + "1: fmt info (all cells)\n", + ) oparser.add_option( "-g", "--gc", type="int", default=0, @@ -299,8 +292,8 @@ def main(cmd_args): xlrd.count_records(args[1]) sys.exit(0) if cmd == 'version': - print "xlrd: %s, from %s" % (xlrd_version, xlrd.__file__) - print "Python:", sys.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')) @@ -311,48 +304,43 @@ def main(cmd_args): 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 + 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 + 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" - if PSYCO: - import psyco - psyco.full() - PSYCO = 0 + print("GC before open:", n_unreachable, "unreachable objects") try: t0 = time.time() - bk = xlrd.open_workbook(fname, + bk = xlrd.open_workbook( + fname, verbosity=options.verbosity, logfile=logfile, - pickleable=options.pickleable, use_mmap=mmap_arg, + 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: - e0, e1 = sys.exc_info()[:2] - print "*** Open failed: %s: %s" % (e0.__name__, e1) + 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 ***" + print("*** KeyboardInterrupt ***") traceback.print_exc(file=sys.stdout) sys.exit(1) - except: - e0, e1 = sys.exc_info()[:2] - print "*** Open failed: %s: %s" % (e0.__name__, e1) + except BaseException as e: + print("*** Open failed: %s: %s" % (type(e).__name__, e)) traceback.print_exc(file=sys.stdout) continue t0 = time.time() @@ -380,16 +368,16 @@ def main(cmd_args): elif cmd == 'xfc': count_xfs(bk) else: - print "*** Unknown command <%s>" % cmd + 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" + 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,) + print("\ncommand took %.2f seconds\n" % (t1-t0,)) return None @@ -398,13 +386,14 @@ def main(cmd_args): main(av) firstarg = av[0].lower() if firstarg == "hotshot": - import hotshot, hotshot.stats + 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) + print("result", repr(result)) prof.close() stats = hotshot.stats.load(prof_log_name) stats.strip_dirs() @@ -417,8 +406,5 @@ def main(cmd_args): import pstats p = pstats.Stats('YYYY.prof') p.strip_dirs().sort_stats('cumulative').print_stats(30) - elif firstarg == "psyco": - PSYCO = 1 - main(av[1:]) else: main(av) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..ed8a958e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[metadata] +license_file = LICENSE diff --git a/setup.py b/setup.py index 29362c27..1a08892a 100644 --- a/setup.py +++ b/setup.py @@ -1,77 +1,45 @@ -#!/usr/bin/env python - -from os import path -import sys -python_version = sys.version_info[:2] - -av = sys.argv -if len(av) > 1 and av[1].lower() == "--egg": - if python_version < (2, 3): - raise Exception("Can't lay eggs with Python version %d.%d " % python_version) - del av[1] - from setuptools import setup -else: - from distutils.core import setup - -the_url = 'http://www.lexicon.net/sjmachin/xlrd.htm' - -# Get version number without importing xlrd/__init__ -# (this horrificness is needed while using 2to3 for -# python 3 compatibility, it should go away once -# we stop using that.) -sys.path.insert(0, path.join(path.dirname(__file__), 'xlrd')) -from info import __VERSION__ -sys.path.pop(0) - -def mkargs(**kwargs): - return kwargs - -args = mkargs( - name = 'xlrd', - version = __VERSION__, - author = 'John Machin', - author_email = 'sjmachin@lexicon.net', - url = the_url, - packages = ['xlrd'], - scripts = [ +from 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) spreadsheet files', - long_description = \ - "Extract data from Excel spreadsheets (.xls and .xlsx, versions 2.0 onwards) on any platform. " \ - "Pure Python (2.6 to 2.7). Strong support for Excel dates. Unicode-aware.", - platforms = ["Any platform -- don't need Windows"], - license = 'BSD', - keywords = ['xls', 'excel', 'spreadsheet', 'workbook'], + ], + 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'] ) - -if python_version >= (2, 3): - args23 = mkargs( - download_url = the_url, - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Operating System :: OS Independent', - 'Topic :: Database', - 'Topic :: Office/Business', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - ) - args.update(args23) - -if python_version >= (2, 4): - args24 = mkargs( - package_data={ - 'xlrd': [ - 'doc/*.htm*', - # 'doc/*.txt', - 'examples/*.*', - ], - - }, - ) - args.update(args24) - -setup(**args) +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/helpers.py b/tests/helpers.py new file mode 100644 index 00000000..94de831b --- /dev/null +++ b/tests/helpers.py @@ -0,0 +1,5 @@ +import os + + +def from_sample(filename): + return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'samples', filename) diff --git a/tests/samples/Formate.xls b/tests/samples/Formate.xls new file mode 100644 index 00000000..808cafb4 Binary files /dev/null and b/tests/samples/Formate.xls differ diff --git a/tests/samples/biff4_no_format_no_window2.xls b/tests/samples/biff4_no_format_no_window2.xls new file mode 100644 index 00000000..ffd633c1 Binary files /dev/null and b/tests/samples/biff4_no_format_no_window2.xls differ diff --git a/tests/samples/corrupted_error.xls b/tests/samples/corrupted_error.xls new file mode 100644 index 00000000..8da3cfab Binary files /dev/null and b/tests/samples/corrupted_error.xls differ diff --git a/tests/samples/formula_test_names.xls b/tests/samples/formula_test_names.xls new file mode 100644 index 00000000..b6d98c7a Binary files /dev/null and b/tests/samples/formula_test_names.xls differ diff --git a/tests/samples/formula_test_sjmachin.xls b/tests/samples/formula_test_sjmachin.xls new file mode 100644 index 00000000..82170088 Binary files /dev/null and b/tests/samples/formula_test_sjmachin.xls differ diff --git a/tests/samples/invalid_formula.xls b/tests/samples/invalid_formula.xls new file mode 100644 index 00000000..6fa82dff Binary files /dev/null and b/tests/samples/invalid_formula.xls differ diff --git a/tests/samples/issue20.xls b/tests/samples/issue20.xls new file mode 100644 index 00000000..d017343a Binary files /dev/null and b/tests/samples/issue20.xls differ diff --git a/xlrd/examples/namesdemo.xls b/tests/samples/namesdemo.xls similarity index 100% rename from xlrd/examples/namesdemo.xls rename to tests/samples/namesdemo.xls diff --git a/tests/samples/picture_in_cell.xls b/tests/samples/picture_in_cell.xls new file mode 100644 index 00000000..fa62e287 Binary files /dev/null and b/tests/samples/picture_in_cell.xls differ diff --git a/tests/samples/profiles.xls b/tests/samples/profiles.xls new file mode 100644 index 00000000..1254f02f Binary files /dev/null and b/tests/samples/profiles.xls differ diff --git a/tests/samples/ragged.xls b/tests/samples/ragged.xls new file mode 100644 index 00000000..2bef15b4 Binary files /dev/null and b/tests/samples/ragged.xls differ diff --git a/tests/samples/sample.ods b/tests/samples/sample.ods new file mode 100644 index 00000000..b3edf0a6 Binary files /dev/null and b/tests/samples/sample.ods differ diff --git a/tests/samples/sample.txt b/tests/samples/sample.txt new file mode 100644 index 00000000..5adcd3d8 --- /dev/null +++ b/tests/samples/sample.txt @@ -0,0 +1 @@ +This is a text file. diff --git a/tests/samples/sample.xlsb b/tests/samples/sample.xlsb new file mode 100644 index 00000000..d47d6029 Binary files /dev/null and b/tests/samples/sample.xlsb differ diff --git a/tests/samples/sample.xlsx b/tests/samples/sample.xlsx new file mode 100644 index 00000000..2f8e5e1b Binary files /dev/null and b/tests/samples/sample.xlsx differ diff --git a/tests/samples/sample.zip b/tests/samples/sample.zip new file mode 100644 index 00000000..808352d3 Binary files /dev/null and b/tests/samples/sample.zip differ diff --git a/tests/samples/xf_class.xls b/tests/samples/xf_class.xls new file mode 100644 index 00000000..41db86cc Binary files /dev/null and b/tests/samples/xf_class.xls differ diff --git a/tests/test_biffh.py b/tests/test_biffh.py new file mode 100644 index 00000000..96444e5d --- /dev/null +++ b/tests/test_biffh.py @@ -0,0 +1,23 @@ +import 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() diff --git a/tests/test_cell.py b/tests/test_cell.py new file mode 100644 index 00000000..42a046ed --- /dev/null +++ b/tests/test_cell.py @@ -0,0 +1,50 @@ +# 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)) diff --git a/tests/test_formats.py b/tests/test_formats.py new file mode 100644 index 00000000..446f6430 --- /dev/null +++ b/tests/test_formats.py @@ -0,0 +1,77 @@ +# -*- 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) diff --git a/tests/test_formulas.py b/tests/test_formulas.py new file mode 100644 index 00000000..2b1a9065 --- /dev/null +++ b/tests/test_formulas.py @@ -0,0 +1,90 @@ +# -*- 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'") + + def test_evaluate_name_formula_with_invalid_operand(self): + book = xlrd.open_workbook(from_sample('invalid_formula.xls')) + sheet = book.sheet_by_index(0) + cell = sheet.cell(0, 0) + + self.assertEqual(cell.ctype, xlrd.XL_CELL_ERROR) + self.assertIn(cell.value, xlrd.error_text_from_code) + diff --git a/tests/test_ignore_workbook_corruption_error.py b/tests/test_ignore_workbook_corruption_error.py new file mode 100644 index 00000000..0cc4fd6f --- /dev/null +++ b/tests/test_ignore_workbook_corruption_error.py @@ -0,0 +1,15 @@ +from 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) diff --git a/tests/test_inspect.py b/tests/test_inspect.py new file mode 100644 index 00000000..2d1a303d --- /dev/null +++ b/tests/test_inspect.py @@ -0,0 +1,32 @@ +from 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 diff --git a/tests/test_missing_records.py b/tests/test_missing_records.py new file mode 100644 index 00000000..9b394731 --- /dev/null +++ b/tests/test_missing_records.py @@ -0,0 +1,22 @@ +from 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) diff --git a/tests/test_open_workbook.py b/tests/test_open_workbook.py new file mode 100644 index 00000000..771bceca --- /dev/null +++ b/tests/test_open_workbook.py @@ -0,0 +1,36 @@ +import 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')) diff --git a/tests/test_sheet.py b/tests/test_sheet.py new file mode 100644 index 00000000..877043b9 --- /dev/null +++ b/tests/test_sheet.py @@ -0,0 +1,160 @@ +# 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) diff --git a/tests/test_workbook.py b/tests/test_workbook.py new file mode 100644 index 00000000..9efd9c29 --- /dev/null +++ b/tests/test_workbook.py @@ -0,0 +1,63 @@ +# 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) diff --git a/tests/test_xldate.py b/tests/test_xldate.py new file mode 100644 index 00000000..fb51e60c --- /dev/null +++ b/tests/test_xldate.py @@ -0,0 +1,56 @@ +#!/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() diff --git a/tests/test_xldate_to_datetime.py b/tests/test_xldate_to_datetime.py new file mode 100644 index 00000000..2984b8da --- /dev/null +++ b/tests/test_xldate_to_datetime.py @@ -0,0 +1,165 @@ +############################################################################### +# +# 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) diff --git a/xlrd/__init__.py b/xlrd/__init__.py index 32d70958..84d5f269 100644 --- a/xlrd/__init__.py +++ b/xlrd/__init__.py @@ -1,471 +1,213 @@ -from os import path - -from info import __VERSION__ - -#

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 licences - -## -#

A Python module for extracting data from MS Excel (TM) spreadsheet files. -#

-# Version 0.7.4 -- April 2012 -#

-# -#

General information

-# -#

Acknowledgements

-# -#

-# Development of this module 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. -#

-# -#

Backporting to Python 2.1 was partially funded by -# -# Journyx - provider of timesheet and project accounting solutions. -# -#

-# -#

Provision of formatting information in version 0.6.1 was funded by -# -# Simplistix Ltd. -# -#

-# -#

Unicode

-# -#

This module presents all text strings as Python unicode objects. -# From Excel 97 onwards, text in Excel spreadsheets has been stored as Unicode. -# 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 UnicodeDecodeError exception will be raised and -# you will need to determine the encoding yourself, and tell xlrd: -#

-#     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: -#

-#     runxlrd.py -e koi8_r 3rows myfile.xls
-# 

-#

The first place to look for an encoding ("codec name") is -# -# the Python documentation. -#

-#
-# -#

Dates in Excel spreadsheets

-# -#

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 -# (a) the "number format" applied to them in Excel and/or -# (b) knowing which cells are supposed to have dates in them. -# This module helps with (a) 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 -# module's xldate_as_tuple function to convert numbers from a workbook, -# you must use the datemode attribute of the 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: -# http://support.microsoft.com/default.aspx?scid=KB;EN-US;q180162

-# -# -#

(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. 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 -- you'll see 1900-02-28 displayed.

-# -#

Reference: http://support.microsoft.com/default.aspx?scid=kb;en-us;214326

-# -#

(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 xldate_* functions in this module -# take the view that such a number is a calendar-independent time of day (like Python's 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 xldate_* functions.

-# -#

Named references, constants, formulas, and macros

-# -#

-# 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 Book object provides two dictionaries (name_map and -# name_and_scope_map) and a list (name_obj_list) which allow various -# ways of accessing the Name objects. There is one Name object for -# each NAME record found in the workbook. 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", -# Name.cell(), which extracts the value in the case where the name -# refers to a single cell. More convenience methods are planned. The -# source code for Name.cell (in __init__.py) is an extra source of -# information on how the Name attributes hang together. -#

-# -#

Name information is not extracted from files older than -# Excel 5.0 (Book.biff_version < 50)

-# -#

Formatting

-# -#

Introduction

-# -#

This collection of features, new in xlrd version 0.6.1, is intended -# to provide the information needed to (1) display/render spreadsheet contents -# (say) on a screen or in a PDF file, and (2) copy spreadsheet data to another -# file without losing the ability to display/render it.

-# -#

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, Rowinfo class) is used if available. -# Failing that, column default information (COLINFO record, 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 version 0.6.1

-#
    -#
  • Rich text i.e. strings containing partial bold italic -# and underlined text, change of font inside a string, etc. -# See OOo docs s3.4 and s3.2. -# Rich text is included in version 0.7.2
  • -#
  • 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 that this does not affect users who are copying XLS -# files, only those who are visually rendering cells.
  • -#
-# -#

Loading worksheets on demand

-# -#

This feature, new in version 0.7.1, is governed by the on_demand argument -# to the 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. open_workbook() loads global data -# and all sheets, releases resources no longer required (principally the -# str or 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: open_workbook() loads global -# data and returns without releasing resources. At this stage, the only -# information available about sheets is Book.nsheets and Book.sheet_names().

-# -#

Book.sheet_by_name() and Book.sheet_by_index() will load the requested -# sheet if it is not already loaded.

-# -#

Book.sheets() will load all/any unloaded sheets.

-# -#

The caller may save memory by calling -# Book.unload_sheet(sheet_name_or_index) when finished with the sheet. -# This applies irrespective of the state of on_demand.

-# -#

The caller may re-load an unloaded sheet by calling Book.sheet_by_xxxx() -# -- except if those 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: -# Book.sheet_loaded(sheet_name_or_index) -> a bool

-# -#

Book.release_resources() may used to save memory and close -# any memory-mapped file before proceding 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 -# 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 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 Book.release_resources() -# explicitly in the finally suite of a try/finally block. -# New in xlrd 0.7.2: the Book object is a "context manager", so if -# using Python 2.5 or later, you can wrap your code in a "with" -# statement.

-## - -import sys, zipfile, pprint -import timemachine -from biffh import ( - XLRDError, - biff_text_from_num, +# 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, - XL_CELL_BLANK, - XL_CELL_TEXT, - XL_CELL_BOOLEAN, - XL_CELL_ERROR, - XL_CELL_EMPTY, - XL_CELL_DATE, - XL_CELL_NUMBER - ) -from formula import * # is constrained by __all__ -from book import Book, colname #### TODO #### formula also has `colname` (restricted to 256 cols) -from sheet import empty_cell -from xldate import XLDateError, xldate_as_tuple - -if sys.version.startswith("IronPython"): - # print >> sys.stderr, "...importing encodings" - import encodings - -try: - import mmap - MMAP_AVAILABLE = 1 -except ImportError: - MMAP_AVAILABLE = 0 -USE_MMAP = MMAP_AVAILABLE - -## -# -# 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 pickleable Default is true. In Python 2.4 or earlier, setting to false -# will cause use of array.array objects which save some memory but can't be pickled. -# In Python 2.5, array.arrays are used unconditionally. Note: if you have large files that -# you need to read multiple times, it can be much faster to cPickle.dump() the xlrd.Book object -# once, and use cPickle.load() multiple times. -# @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 ... as a string or an 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. Refer to discussion in the Unicode section above. -#
-- New in version 0.6.0 -# -# @param formatting_info Governs provision of a reference to an XF (eXtended Format) object -# for each cell in the worksheet. -#
Default is False. This is backwards compatible and saves memory. -# "Blank" cells (those with their own formatting information but no data) are treated as empty -# (by ignoring the file's BLANK and MULBLANK records). -# It cuts off any bottom "margin" of rows of empty (and blank) cells and -# any right "margin" of columns of empty (and blank) cells. -# Only cell_value and cell_type are available. -#
True provides all cells, including empty and blank cells. -# XF information is available for each cell. -#
-- New in version 0.6.1 -# -# @param on_demand Governs whether sheets are all loaded initially or when demanded -# by the caller. Please refer back to the section "Loading worksheets on demand" for details. -#
-- New in version 0.7.1 -# -# @param ragged_rows False (the default) means all rows are padded out with empty cells so that all -# rows have the same size (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 -# Sheet.row_len() method. -#
-- New in version 0.7.2 -# -# @return An instance of the Book class. +) +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 -def open_workbook(filename=None, - logfile=sys.stdout, - verbosity=0, - pickleable=True, - use_mmap=USE_MMAP, - file_contents=None, - encoding_override=None, - formatting_info=False, - on_demand=False, - ragged_rows=False, - ): - peeksz = 4 - if file_contents: - peek = file_contents[:peeksz] + +#: 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: - f = open(filename, "rb") - peek = f.read(peeksz) - f.close() - if peek == timemachine.BYTES_LITERAL("PK\x03\x04"): # a ZIP file - if file_contents: - zf = zipfile.ZipFile(timemachine.BYTES_IO(file_contents)) - else: - zf = zipfile.ZipFile(filename) - component_names = zf.namelist() - if verbosity: - logfile.write('ZIP component_names:\n') - pprint.pprint(component_names, logfile) + 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: - import xlsx - bk = xlsx.open_workbook_2007_xml( - zf, - component_names, - logfile=logfile, - verbosity=verbosity, - pickleable=pickleable, - use_mmap=mmap, - formatting_info=formatting_info, - on_demand=on_demand, - ragged_rows=ragged_rows, - ) - return bk + return 'xlsx' if 'xl/workbook.bin' in component_names: - raise XLRDError('Excel 2007 xlsb file; not supported') + return 'xlsb' if 'content.xml' in component_names: - raise XLRDError('Openoffice.org ODS file; not supported') - raise XLRDError('ZIP file contents not a known type of workbook') + 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. - import book - bk = book.open_workbook_xls( + 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, - pickleable=pickleable, 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 -## -# 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). def dump(filename, outfile=sys.stdout, unnumbered=False): - from book import Book - from biffh import biff_dump + """ + 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) -## -# For debugging and analysis: summarise the file's BIFF records. -# I.e. 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. def count_records(filename, outfile=sys.stdout): - from book import Book - from biffh import biff_count_records + """ + 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) diff --git a/xlrd/biffh.py b/xlrd/biffh.py index d5855be9..07ac6291 100644 --- a/xlrd/biffh.py +++ b/xlrd/biffh.py @@ -1,45 +1,40 @@ -# -*- coding: cp1252 -*- - -## -# Support module for the xlrd package. -# -#

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.

-## - -# 2010-03-01 SJM Reading SCL record -# 2010-03-01 SJM Added more record IDs for biff_dump & biff_count -# 2008-02-10 SJM BIFF2 BLANK record -# 2008-02-08 SJM Preparation for Excel 2.0 support -# 2008-02-02 SJM Added suffixes (_B2, _B2_ONLY, etc) on record names for biff_dump & biff_count -# 2007-12-04 SJM Added support for Excel 2.x (BIFF2) files. -# 2007-09-08 SJM Avoid crash when zero-length Unicode string missing options byte. -# 2007-04-22 SJM Remove experimental "trimming" facility. +# -*- 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 -from struct import unpack -import sys -from timemachine import * + class XLRDError(Exception): - pass + """ + An exception indicating problems reading data from an Excel file. + """ -## -# Parent of almost all other classes in the package. Defines a common "dump" method -# for debugging. class BaseObject(object): + """ + Parent of almost all other classes in the package. Defines a common + :meth:`dump` method for debugging. + """ _repr_these = [] - ## - # @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) 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__"): @@ -48,9 +43,9 @@ def dump(self, f=None, header=None, footer=None, indent=0): alist.append((attr, getattr(self, attr))) else: alist = self.__dict__.items() - alist.sort() + alist = sorted(alist) pad = " " * indent - if header is not None: print >> f, header + if header is not None: print(header, file=f) list_type = type([]) dict_type = type({}) for attr, value in alist: @@ -58,13 +53,12 @@ def dump(self, f=None, header=None, footer=None, indent=0): 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 >> f, "%s%s: %s, len = %d" % (pad, attr, type(value), len(value)) + 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: - print >> f, "%s%s: %r" % (pad, attr, value) - if footer is not None: print >> f, footer + 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 @@ -91,21 +85,10 @@ def dump(self, f=None, header=None, footer=None, indent=0): 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. Here are its contents: -#

-# 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
-# 

+} +#: 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 @@ -245,19 +228,13 @@ def dump(self, f=None, header=None, footer=None, indent=0): XL_NUMBER, XL_RK, XL_RSTRING, - ] +] _cell_opcode_dict = {} for _cell_opcode in _cell_opcode_list: _cell_opcode_dict[_cell_opcode] = 1 -is_cell_opcode = _cell_opcode_dict.has_key - -# def fprintf(f, fmt, *vargs): f.write(fmt % vargs) -def fprintf(f, fmt, *vargs): - if fmt.endswith('\n'): - print >> f, fmt[:-1] % vargs - else: - print >> f, fmt % vargs, +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: @@ -288,9 +265,9 @@ def unpack_unicode(data, pos, lenlen=2): if not nchars: # Ambiguous whether 0-length string should have an "options" byte. # Avoid crash if missing. - return u"" + return UNICODE_LITERAL("") pos += lenlen - options = ord(data[pos]) + options = BYTES_ORD(data[pos]) pos += 1 # phonetic = options & 0x04 # richtext = options & 0x08 @@ -331,8 +308,8 @@ def unpack_unicode_update_pos(data, pos, lenlen=2, known_len=None): pos += lenlen if not nchars and not data[pos:]: # Zero-length string with no options byte - return (u"", pos) - options = ord(data[pos]) + return (UNICODE_LITERAL(""), pos) + options = BYTES_ORD(data[pos]) pos += 1 phonetic = options & 0x04 richtext = options & 0x08 @@ -356,8 +333,7 @@ def unpack_unicode_update_pos(data, pos, lenlen=2, known_len=None): pos += sz return (strg, pos) -def unpack_cell_range_address_list_update_pos( - output_list, data, pos, biff_version, addr_size=6): +def unpack_cell_range_address_list_update_pos(output_list, data, pos, biff_version, addr_size=6): # output_list is updated in situ assert addr_size in (6, 8) # Used to assert size == 6 if not BIFF8, but pyWLWriter writes @@ -557,9 +533,11 @@ def hex_char_dump(strg, ofs, dlen, base=0, fout=sys.stdout, unnumbered=False): '??? hex_char_dump: ofs=%d dlen=%d base=%d -> endpos=%d pos=%d endsub=%d substrg=%r\n', ofs, dlen, base, endpos, pos, endsub, substrg) break - hexd = ''.join(["%02x " % ord(c) for c in substrg]) + 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 <= '~'): @@ -567,6 +545,7 @@ def hex_char_dump(strg, ofs, dlen, base=0, fout=sys.stdout, unnumbered=False): chard += c if numbered: num_prefix = "%5d: " % (base+pos-ofs) + fprintf(fout, "%s %-48s %s\n", num_prefix, hexd, chard) pos = endsub @@ -580,7 +559,7 @@ def biff_dump(mem, stream_offset, stream_len, base=0, fout=sys.stdout, unnumbere while stream_end - pos >= 4: rc, length = unpack('= 4: rc, length = unpack('> fout, "%8d %s" % (count, recname) + print("%8d %s" % (count, recname), file=fout) encoding_from_codepage = { 1200 : 'utf_16_le', @@ -650,7 +628,7 @@ def biff_count_records(mem, stream_offset, stream_len, fout=sys.stdout): 10081: 'mac_turkish', # guess 32768: 'mac_roman', 32769: 'cp1252', - } +} # some more guessing, for Indic scripts # codepage 57000 range: # 2 Devanagari [0] diff --git a/xlrd/book.py b/xlrd/book.py index 0fad0582..5c773712 100644 --- a/xlrd/book.py +++ b/xlrd/book.py @@ -1,39 +1,29 @@ -# -*- coding: ascii -*- - -#

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.

- -from timemachine import * -from biffh import * -import struct; unpack = struct.unpack -import sys -import time -import sheet -import compdoc -from xldate import xldate_as_tuple, XLDateError -from formula import * -import formatting -if sys.version.startswith("IronPython"): - # print >> sys.stderr, "...importing encodings" - import encodings +# 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. -empty_cell = sheet.empty_cell # for exposure to the world ... - -DEBUG = 0 +from __future__ import print_function -USE_FANCY_CD = 1 +import struct -TOGGLE_GC = 0 -import gc -# gc.set_debug(gc.DEBUG_STATS) +from . import compdoc, formatting, sheet +from .biffh import * +from .formula import * +from .timemachine import * try: - import mmap - MMAP_AVAILABLE = 1 + from time import perf_counter except ImportError: - MMAP_AVAILABLE = 0 -USE_MMAP = MMAP_AVAILABLE + # Python 2.7 + from time import clock as perf_counter + +from struct import unpack + +empty_cell = sheet.empty_cell # for exposure to the world ... + +DEBUG = 0 + +import mmap MY_EOF = 0xF00BAAA # not a 16-bit number @@ -41,49 +31,50 @@ SUPPORTED_VERSIONS = (80, 70, 50, 45, 40, 30, 21, 20) -code_from_builtin_name = { - u"Consolidate_Area": u"\x00", - u"Auto_Open": u"\x01", - u"Auto_Close": u"\x02", - u"Extract": u"\x03", - u"Database": u"\x04", - u"Criteria": u"\x05", - u"Print_Area": u"\x06", - u"Print_Titles": u"\x07", - u"Recorder": u"\x08", - u"Data_Form": u"\x09", - u"Auto_Activate": u"\x0A", - u"Auto_Deactivate": u"\x0B", - u"Sheet_Title": u"\x0C", - u"_FilterDatabase": u"\x0D", - } +_code_from_builtin_name = { + "Consolidate_Area": "\x00", + "Auto_Open": "\x01", + "Auto_Close": "\x02", + "Extract": "\x03", + "Database": "\x04", + "Criteria": "\x05", + "Print_Area": "\x06", + "Print_Titles": "\x07", + "Recorder": "\x08", + "Data_Form": "\x09", + "Auto_Activate": "\x0A", + "Auto_Deactivate": "\x0B", + "Sheet_Title": "\x0C", + "_FilterDatabase": "\x0D", +} builtin_name_from_code = {} -for _bin, _bic in code_from_builtin_name.items(): +code_from_builtin_name = {} +for _bin, _bic in _code_from_builtin_name.items(): + _bin = UNICODE_LITERAL(_bin) + _bic = UNICODE_LITERAL(_bic) + code_from_builtin_name[_bin] = _bic builtin_name_from_code[_bic] = _bin -del _bin, _bic +del _bin, _bic, _code_from_builtin_name def open_workbook_xls(filename=None, - logfile=sys.stdout, verbosity=0, pickleable=True, use_mmap=USE_MMAP, - file_contents=None, - encoding_override=None, - formatting_info=False, on_demand=False, ragged_rows=False, - ): - t0 = time.clock() - if TOGGLE_GC: - orig_gc_enabled = gc.isenabled() - if orig_gc_enabled: - gc.disable() + 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): + t0 = perf_counter() bk = Book() try: bk.biff2_8_load( filename=filename, file_contents=file_contents, - logfile=logfile, verbosity=verbosity, pickleable=pickleable, use_mmap=use_mmap, + logfile=logfile, verbosity=verbosity, use_mmap=use_mmap, encoding_override=encoding_override, formatting_info=formatting_info, on_demand=on_demand, ragged_rows=ragged_rows, - ) - t1 = time.clock() + ignore_workbook_corruption=ignore_workbook_corruption + ) + t1 = perf_counter() bk.load_time_stage_1 = t1 - t0 biff_version = bk.getbof(XL_WORKBOOK_GLOBALS) if not biff_version: @@ -92,7 +83,7 @@ def open_workbook_xls(filename=None, raise XLRDError( "BIFF version %s is not supported" % biff_text_from_num[biff_version] - ) + ) bk.biff_version = biff_version if biff_version <= 40: # no workbook globals, only 1 worksheet @@ -116,17 +107,15 @@ def open_workbook_xls(filename=None, bk.get_sheets() bk.nsheets = len(bk._sheet_list) if biff_version == 45 and bk.nsheets > 1: - fprintf(bk.logfile, + 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 - ) - if TOGGLE_GC: - if orig_gc_enabled: - gc.enable() - t2 = time.clock() + ) + t2 = perf_counter() bk.load_time_stage_2 = t2 - t1 - except: + except Exception: bk.release_resources() raise # normal exit @@ -134,107 +123,87 @@ def open_workbook_xls(filename=None, bk.release_resources() return bk -## -# For debugging: dump the 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). - -def dump(filename, outfile=sys.stdout, unnumbered=False): - bk = Book() - bk.biff2_8_load(filename=filename, logfile=outfile, ) - biff_dump(bk.mem, bk.base, bk.stream_len, 0, outfile, unnumbered) - -## -# For debugging and analysis: summarise the file's BIFF records. -# I.e. 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. - -def count_records(filename, outfile=sys.stdout): - bk = Book() - bk.biff2_8_load(filename=filename, logfile=outfile, ) - biff_count_records(bk.mem, bk.base, bk.stream_len, outfile) - -## -# Information relating to a named reference, formula, macro, etc. -#
-- New in version 0.6.0 -#
-- Name information is not extracted from files older than -# Excel 5.0 (Book.biff_version < 50) 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 + #: 0 = Visible; 1 = Hidden hidden = 0 - ## - # 0 = Command macro; 1 = Function macro. Relevant only if macro == 1 + #: 0 = Command macro; 1 = Function macro. Relevant only if macro == 1 func = 0 - ## - # 0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1 + #: 0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1 vbasic = 0 - ## - # 0 = Standard name; 1 = Macro name + #: 0 = Standard name; 1 = Macro name macro = 0 - ## - # 0 = Simple formula; 1 = Complex formula (array formula or user defined)
- # No examples have been sighted. + #: 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) + #: 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. + #: Function group. Relevant only if macro == 1; see OOo docs for values. funcgroup = 0 - ## - # 0 = Formula definition; 1 = Binary data
No examples have been sighted. + #: 0 = Formula definition; 1 = Binary data + #: + #: .. note:: No examples have been sighted. binary = 0 - ## - # The index of this object in book.name_obj_list + #: The index of this object in book.name_obj_list name_index = 0 - ## # A Unicode string. If builtin, decoded as per OOo docs. - name = u"" - - ## - # An 8-bit string. - raw_formula = BYTES_NULL - - ## - # -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. + 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 - # Operand class. + #: 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 - ## - # This is a convenience method for the frequent use case where the name - # refers to a single cell. - # @return An instance of the Cell class. - # @throws XLRDError The name is not a constant absolute reference - # to a single cell. 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 @@ -242,28 +211,36 @@ def cell(self): 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): + 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, + 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") - ## - # 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. - # @return a tuple (sheet_object, rowxlo, rowxhi, colxlo, colxhi). - # @throws XLRDError The name is not a constant absolute reference - # to a single area in a single sheet. 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 @@ -282,189 +259,258 @@ def area2d(self, clipped=True): assert 0 <= rowxlo <= rowxhi <= sh.nrows assert 0 <= colxlo <= colxhi <= sh.ncols return sh, rowxlo, rowxhi, colxlo, colxhi - self.dump(self.book.logfile, + 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") -## -# Contents of a "workbook". -#

WARNING: You don't call this class yourself. You use the Book object that -# was returned when you called xlrd.open_workbook("myfile.xls").

class Book(BaseObject): + """ + Contents of a "workbook". - ## - # The number of worksheets present in the workbook file. - # This information is available even when no sheets have yet been loaded. - nsheets = 0 + .. warning:: - ## - # 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).
- datemode = 0 # In case it's not specified in the file. + You should not instantiate this class yourself. You use the :class:`Book` + object that was returned when you called :func:`~xlrd.open_workbook`. + """ - ## - # 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). + #: 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 Name object for each NAME record in the workbook. - #
-- New in version 0.6.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' + #: 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. + #: The encoding that was derived from the codepage. encoding = None - ## - # A tuple containing the (telephone system) 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. + #: 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 = u'' + #: What (if anything) is recorded as the name of the last user to + #: save the file. + user_name = UNICODE_LITERAL('') - ## - # A list of Font class instances, each corresponding to a FONT record. - #
-- New in version 0.6.1 + #: A list of :class:`~xlrd.formatting.Font` class instances, + #: each corresponding to a FONT record. + #: + #: .. versionadded:: 0.6.1 font_list = [] - ## - # A list of XF class instances, each corresponding to an XF record. - #
-- New in version 0.6.1 + #: A list of :class:`~xlrd.formatting.XF` class instances, + #: each corresponding to an ``XF`` record. + #: + #: .. versionadded:: 0.6.1 xf_list = [] - ## - # A list of 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) pyExcelerator, - # use this list. - # The collection to be used for all visual rendering purposes is format_map. - #
-- New in version 0.6.1 + #: 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 XF.format_key to Format object. - #
-- New in version 0.6.1 + #: 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 1 = built-in style, 0 = user-defined
- # xf_index is an index into Book.xf_list.
- # References: OOo docs s6.99 (STYLE record); Excel UI Format/Style - #
-- New in version 0.6.1; since 0.7.4, extracted only if - # open_workbook(..., formatting_info=True) + #: 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 the - # above section "The Palette; Colour Indexes" 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. - # 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 palette_record. - #
-- New in version 0.6.1. Extracted only if open_workbook(..., formatting_info=True) + #: 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 colour_map. - #
-- New in version 0.6.1. Extracted only if open_workbook(..., formatting_info=True) + #: 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). + #: 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). + #: Time in seconds to parse the data from the contiguous string + #: (or mmap equivalent). load_time_stage_2 = -1.0 - ## - # @return A list of all sheets in the book. - # All sheets not already loaded will be loaded. 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[:] - ## - # @param sheetx Sheet index in range(nsheets) - # @return An object of the Sheet class 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) - ## - # @param sheet_name Name of sheet required - # @return An object of the Sheet class + 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) - ## - # @return 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. + 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[:] - ## - # @param sheet_name_or_index Name or index of sheet enquired upon - # @return true if sheet is loaded, false otherwise - #
-- New in version 0.7.1 def sheet_loaded(self, sheet_name_or_index): - # using type(1) because int won't work with Python 2.1 - if isinstance(sheet_name_or_index, type(1)): + """ + :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 self._sheet_list[sheetx] and True or False # Python 2.1 again + return bool(self._sheet_list[sheetx]) - ## - # @param sheet_name_or_index Name or index of sheet to be unloaded. - #
-- New in version 0.7.1 def unload_sheet(self, sheet_name_or_index): - # using type(1) because int won't work with Python 2.1 - if isinstance(sheet_name_or_index, type(1)): + """ + :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: @@ -472,17 +518,19 @@ def unload_sheet(self, sheet_name_or_index): except ValueError: raise XLRDError('No sheet named <%r>' % sheet_name_or_index) self._sheet_list[sheetx] = None - - ## - # This method has a dual purpose. You can call it to release - # memory-consuming objects and (possibly) a memory-mapped file - # (mmap.mmap object) when you have finished loading sheets in - # on_demand mode, but still require the Book object to examine the - # loaded sheets. It is also called automatically (a) when 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. + 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 @@ -493,24 +541,25 @@ def release_resources(self): 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 + # return false - ## - # A mapping from (lower_case_name, scope) to a single Name object. - #
-- New in version 0.6.0 + #: 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 Name objects. The list is - # sorted in scope order. Typically there will be one item (of global scope) - # in the list. - #
-- New in version 0.6.0 + #: 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): @@ -543,64 +592,39 @@ def __init__(self): self.palette_record = [] self.xf_list = [] self.style_name_map = {} - self.mem = BYTES_NULL - self.filestr = BYTES_NULL + self.mem = b'' + self.filestr = b'' def biff2_8_load(self, filename=None, file_contents=None, - logfile=sys.stdout, verbosity=0, pickleable=True, use_mmap=USE_MMAP, - encoding_override=None, - formatting_info=False, - on_demand=False, - ragged_rows=False, - ): + 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.pickleable = pickleable - self.use_mmap = use_mmap and MMAP_AVAILABLE + 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: - if python_version < (2, 2) and self.use_mmap: - # need to open for update - open_mode = "r+b" - else: - open_mode = "rb" - retry = False - f = None - try: - try: - f = open(filename, open_mode) - except IOError: - e, v = sys.exc_info()[:2] - if open_mode == "r+b" \ - and (v.errno == 13 or v.strerror == "Permission denied"): - # Maybe the file is read-only - retry = True - self.use_mmap = False - else: - raise - if retry: - f = open(filename, "rb") + 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: - if python_version < (2, 2): - self.filestr = mmap.mmap(f.fileno(), size) - else: - self.filestr = mmap.mmap(f.fileno(), size, access=mmap.ACCESS_READ) + 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) - finally: - if f: f.close() else: self.filestr = file_contents self.stream_len = len(file_contents) @@ -610,28 +634,23 @@ def biff2_8_load(self, filename=None, file_contents=None, # got this one at the antique store self.mem = self.filestr else: - cd = compdoc.CompDoc(self.filestr, logfile=self.logfile) - if USE_FANCY_CD: - for qname in [u'Workbook', u'Book']: - self.mem, self.base, self.stream_len = cd.locate_named_stream(qname) - if self.mem: break - else: - raise XLRDError("Can't find workbook in OLE2 compound document") + 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: - for qname in [u'Workbook', u'Book']: - self.mem = cd.get_named_stream(qname) - if self.mem: break - else: - raise XLRDError("Can't find workbook in OLE2 compound document") - self.stream_len = len(self.mem) + 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 = BYTES_NULL + self.filestr = b'' self._position = self.base if DEBUG: - print >> self.logfile, "mem: %s, base: %d, len: %d" % (type(self.mem), self.base, self.stream_len) + 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 :-( @@ -668,7 +687,7 @@ def get_record_parts_conditional(self, reqd_record): mem = self.mem code, length = unpack('> self.logfile, "GET_SHEETS:", self._sheet_names, self._sh_abs_posn + if DEBUG: print("GET_SHEETS:", self._sheet_names, self._sh_abs_posn, file=self.logfile) for sheetno in xrange(len(self._sheet_names)): - if DEBUG: print >> self.logfile, "GET_SHEETS: sheetno =", sheetno, self._sheet_names, self._sh_abs_posn + if DEBUG: print("GET_SHEETS: sheetno =", sheetno, self._sheet_names, self._sh_abs_posn, file=self.logfile) self.get_sheet(sheetno) def fake_globals_get_sheet(self): # for BIFF 4.0 and earlier formatting.initialise_book(self) - fake_sheet_name = u'Sheet 1' + fake_sheet_name = UNICODE_LITERAL('Sheet 1') self._sheet_names = [fake_sheet_name] self._sh_abs_posn = [0] self._sheet_visibility = [0] # one sheet, visible @@ -715,7 +735,7 @@ def handle_boundsheet(self, data): bv = self.biff_version self.derive_encoding() if DEBUG: - fprintf(self.logfile, "BOUNDSHEET: bv=%d data %r\n", bv, data); + fprintf(self.logfile, "BOUNDSHEET: bv=%d data %r\n", bv, data) if bv == 45: # BIFF4W #### Not documented in OOo docs ... # In fact, the *only* data is the name of the sheet. @@ -748,7 +768,7 @@ def handle_boundsheet(self, data): 1: 'Macro sheet', 2: 'Chart', 6: 'Visual Basic module', - }.get(sheet_type, 'UNKNOWN') + }.get(sheet_type, 'UNKNOWN') if DEBUG or self.verbosity >= 1: fprintf(self.logfile, @@ -775,18 +795,21 @@ def derive_encoding(self): elif self.codepage is None: if self.biff_version < 80: fprintf(self.logfile, - "*** No CODEPAGE record, no encoding_override: will use 'ascii'\n") - self.encoding = 'ascii' + "*** No CODEPAGE record, no encoding_override: will use 'iso-8859-1'\n") + self.encoding = 'iso-8859-1' else: self.codepage = 1200 # utf16le if self.verbosity >= 2: fprintf(self.logfile, "*** No CODEPAGE record; assuming 1200 (utf_16_le)\n") else: codepage = self.codepage - if encoding_from_codepage.has_key(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) : @@ -796,12 +819,11 @@ def derive_encoding(self): # If we don't have a codec that can decode ASCII into Unicode, # we're well & truly stuffed -- let the punter know ASAP. try: - _unused = unicode(BYTES_LITERAL('trial'), self.encoding) - except: - ei = sys.exc_info()[:2] + unicode(b'trial', self.encoding) + except BaseException as e: fprintf(self.logfile, "ERROR *** codepage %r -> encoding %r -> %s: %s\n", - self.codepage, self.encoding, ei[0].__name__.split(".")[-1], ei[1]) + 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) @@ -820,7 +842,7 @@ def handle_codepage(self, data): def handle_country(self, data): countries = unpack('> self.logfile, "Countries:", countries + if self.verbosity: print("Countries:", countries, file=self.logfile) # Note: in BIFF7 and earlier, country record was put (redundantly?) in each worksheet. assert self.countries == (0, 0) or self.countries == countries self.countries = countries @@ -860,7 +882,7 @@ def handle_externsheet(self, data): self.logfile, "INFO: EXTERNSHEET needs %d bytes, have %d\n", bytes_reqd, len(data), - ) + ) code2, length2, data2 = self.get_record_parts() if code2 != XL_CONTINUE: raise XLRDError("Missing CONTINUE after EXTERNSHEET record") @@ -876,23 +898,23 @@ def handle_externsheet(self, data): self.logfile, "EXTERNSHEET(b8): k = %2d, record = %2d, first_sheet = %5d, last sheet = %5d\n", k, ref_recordx, ref_first_sheetx, ref_last_sheetx, - ) + ) else: nc, ty = unpack("> self.logfile, "EXTERNSHEET(b7-):" + print("EXTERNSHEET(b7-):", file=self.logfile) hex_char_dump(data, 0, len(data), fout=self.logfile) msg = { 1: "Encoded URL", 2: "Current sheet!!", 3: "Specific sheet in own doc't", 4: "Nonspecific sheet in own doc't!!", - }.get(ty, "Not encoded") - print >> self.logfile, " %3d chars, type is %d (%s)" % (nc, ty, msg) + }.get(ty, "Not encoded") + print(" %3d chars, type is %d (%s)" % (nc, ty, msg), file=self.logfile) if ty == 3: sheet_name = unicode(data[2:nc+2], self.encoding) self._extnsht_name_from_num[self._extnsht_count] = sheet_name - if blah2: print >> self.logfile, self._extnsht_name_from_num + if blah2: print(self._extnsht_name_from_num, file=self.logfile) if not (1 <= ty <= 4): ty = 0 self._externsheet_type_b57.append(ty) @@ -929,8 +951,8 @@ def handle_name(self, data): # 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, + 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] @@ -959,13 +982,15 @@ def handle_name(self, data): nobj.excel_sheet_index = sheet_index nobj.scope = None # patched up in the names_epilogue() method if blah: - print >> self.logfile, "NAME[%d]:%s oflags=%d, name_len=%d, fmla_len=%d, extsht_index=%d, sheet_index=%d, name=%r" \ - % (name_index, macro_flag, option_flags, name_len, + 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 >> self.logfile, " builtin: %s" % name + if blah: print(" builtin: %s" % name, file=self.logfile) nobj.name = name nobj.raw_formula = data[pos:] nobj.basic_formula_len = fmla_len @@ -975,16 +1000,16 @@ def handle_name(self, data): self.logfile, header="--- handle_name: name[%d] ---" % name_index, footer="-------------------", - ) + ) def names_epilogue(self): blah = self.verbosity >= 2 f = self.logfile if blah: - print >> f, "+++++ names_epilogue +++++" - print >> f, "_all_sheets_map", self._all_sheets_map - print >> f, "_extnsht_name_from_num", self._extnsht_name_from_num - print >> f, "_sheet_num_from_name", self._sheet_num_from_name + 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] @@ -1020,11 +1045,11 @@ def names_epilogue(self): evaluate_name_formula(self, nobj, namex, blah=blah) if self.verbosity >= 2: - print >> f, "---------- name object dump ----------" + 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 >> f, "--------------------------------------" + print("--------------------------------------", file=f) # # Build some dicts for access to the name objects # @@ -1034,22 +1059,20 @@ def names_epilogue(self): nobj = self.name_obj_list[namex] name_lcase = nobj.name.lower() key = (name_lcase, nobj.scope) - if name_and_scope_map.has_key(key): - msg = 'Duplicate entry %r in name_and_scope_map' % (key, ) - if 0: - raise XLRDError(msg) - else: - if self.verbosity: - print >> f, msg + 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 - if name_map.has_key(name_lcase): - name_map[name_lcase].append((nobj.scope, 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] = [(nobj.scope, nobj)] + name_map[name_lcase] = [sort_data] for key in name_map.keys(): alist = name_map[key] alist.sort() - name_map[key] = [x[1] for x in alist] + name_map[key] = [x[2] for x in alist] self.name_and_scope_map = name_and_scope_map self.name_map = name_map @@ -1065,31 +1088,31 @@ def handle_supbook(self, data): self._supbook_types.append(None) blah = DEBUG or self.verbosity >= 2 if blah: - print >> self.logfile, "SUPBOOK:" + print("SUPBOOK:", file=self.logfile) hex_char_dump(data, 0, len(data), fout=self.logfile) num_sheets = unpack("> self.logfile, "num_sheets = %d" % num_sheets + if blah: print("num_sheets = %d" % num_sheets, file=self.logfile) sbn = self._supbook_count self._supbook_count += 1 - if data[2:4] == BYTES_LITERAL("\x01\x04"): + if data[2:4] == b"\x01\x04": self._supbook_types[-1] = SUPBOOK_INTERNAL self._supbook_locals_inx = self._supbook_count - 1 if blah: - print >> self.logfile, "SUPBOOK[%d]: internal 3D refs; %d sheets" % (sbn, num_sheets) - print >> self.logfile, " _all_sheets_map", self._all_sheets_map + print("SUPBOOK[%d]: internal 3D refs; %d sheets" % (sbn, num_sheets), file=self.logfile) + print(" _all_sheets_map", self._all_sheets_map, file=self.logfile) return - if data[0:4] == BYTES_LITERAL("\x01\x00\x01\x3A"): + if data[0:4] == b"\x01\x00\x01\x3A": self._supbook_types[-1] = SUPBOOK_ADDIN self._supbook_addins_inx = self._supbook_count - 1 - if blah: print >> self.logfile, "SUPBOOK[%d]: add-in functions" % sbn + if blah: print("SUPBOOK[%d]: add-in functions" % sbn, file=self.logfile) return url, pos = unpack_unicode_update_pos(data, 2, lenlen=2) if num_sheets == 0: self._supbook_types[-1] = SUPBOOK_DDEOLE - if blah: print >> self.logfile, "SUPBOOK[%d]: DDE/OLE document = %r" % (sbn, url) + if blah: fprintf(self.logfile, "SUPBOOK[%d]: DDE/OLE document = %r\n", sbn, url) return self._supbook_types[-1] = SUPBOOK_EXTERNAL - if blah: print >> self.logfile, "SUPBOOK[%d]: url = %r" % (sbn, url) + if blah: fprintf(self.logfile, "SUPBOOK[%d]: url = %r\n", sbn, url) sheet_names = [] for x in range(num_sheets): try: @@ -1098,13 +1121,14 @@ def handle_supbook(self, data): # #### FIX ME #### # Should implement handling of CONTINUE record(s) ... if self.verbosity: - print >> self.logfile, ( - "*** WARNING: unpack failure in sheet %d of %d in SUPBOOK record for file %r" - % (x, num_sheets, url) - ) + print( + "*** WARNING: unpack failure in sheet %d of %d in SUPBOOK record for file %r" + % (x, num_sheets, url), + file=self.logfile, + ) break sheet_names.append(shname) - if blah: print >> self.logfile, " sheetx=%d namelen=%d name=%r (next pos=%d)" % (x, len(shname), shname, pos) + if blah: fprintf(self.logfile, " sheetx=%d namelen=%d name=%r (next pos=%d)\n", x, len(shname), shname, pos) def handle_sheethdr(self, data): # This a BIFF 4W special. @@ -1119,25 +1143,25 @@ def handle_sheethdr(self, data): self._sheethdr_count += 1 BOF_posn = self._position posn = BOF_posn - 4 - len(data) - if DEBUG: print >> self.logfile, 'SHEETHDR %d at posn %d: len=%d name=%r' % (sheetno, posn, sheet_len, sheet_name) + if DEBUG: fprintf(self.logfile, 'SHEETHDR %d at posn %d: len=%d name=%r\n', sheetno, posn, sheet_len, sheet_name) self.initialise_format_info() - if DEBUG: print >> self.logfile, 'SHEETHDR: xf epilogue flag is %d' % self._xf_epilogue_done + if DEBUG: print('SHEETHDR: xf epilogue flag is %d' % self._xf_epilogue_done, file=self.logfile) self._sheet_list.append(None) # get_sheet updates _sheet_list but needs a None beforehand self.get_sheet(sheetno, update_pos=False) - if DEBUG: print >> self.logfile, 'SHEETHDR: posn after get_sheet() =', self._position + if DEBUG: print('SHEETHDR: posn after get_sheet() =', self._position, file=self.logfile) self._position = BOF_posn + sheet_len def handle_sheetsoffset(self, data): # DEBUG = 0 posn = unpack('> self.logfile, 'SHEETSOFFSET:', posn + if DEBUG: print('SHEETSOFFSET:', posn, file=self.logfile) self._sheetsoffset = posn def handle_sst(self, data): # DEBUG = 1 if DEBUG: - print >> self.logfile, "SST Processing" - t0 = time.time() + print("SST Processing", file=self.logfile) + t0 = perf_counter() nbt = len(data) strlist = [data] uniquestrings = unpack('> self.logfile, "SST processing took %.2f seconds" % (t1 - t0, ) + t1 = perf_counter() + print("SST processing took %.2f seconds" % (t1 - t0, ), file=self.logfile) def handle_writeaccess(self, data): - # DEBUG = 0 + DEBUG = 0 if self.biff_version < 80: if not self.encoding: self.raw_user_name = True @@ -1167,8 +1191,12 @@ def handle_writeaccess(self, data): return strg = unpack_string(data, 0, self.encoding, lenlen=1) else: - strg = unpack_unicode(data, 0, lenlen=2) - if DEBUG: print >> self.logfile, "WRITEACCESS: %d bytes; raw=%d %r" % (len(data), self.raw_user_name, strg) + 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 @@ -1178,7 +1206,7 @@ def parse_globals(self): formatting.initialise_book(self) while 1: rc, length, data = self.get_record_parts() - if DEBUG: print >> self.logfile, "parse_globals: record code is 0x%04x" % rc + 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: @@ -1216,8 +1244,8 @@ def parse_globals(self): elif rc == XL_STYLE: self.handle_style(data) elif rc & 0xff == 9 and self.verbosity: - print >> self.logfile, "*** Unexpected BOF at posn %d: 0x%04x len=%d data=%r" \ - % (self._position - length - 4, rc, length, data) + 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() @@ -1226,7 +1254,7 @@ def parse_globals(self): self.derive_encoding() if self.biff_version == 45: # DEBUG = 0 - if DEBUG: print >> self.logfile, "global EOF: position", self._position + if DEBUG: print("global EOF: position", self._position, file=self.logfile) # if DEBUG: # pos = self._position - 4 # print repr(self.mem[pos:pos+40]) @@ -1244,7 +1272,8 @@ def read(self, pos, length): def getbof(self, rqd_stream): # DEBUG = 1 # if DEBUG: print >> self.logfile, "getbof(): position", self._position - if DEBUG: print >> self.logfile, "reqd: 0x%04x" % rqd_stream + 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 @@ -1260,21 +1289,21 @@ def bof_error(msg): bof_error( 'Invalid length (%d) for BOF record type 0x%04x' % (length, opcode)) - padding = BYTES_X00 * max(0, boflen[opcode] - length) - data = self.read(self._position, length); - if DEBUG: print >> self.logfile, "\ngetbof(): data=%r" % data + 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('> self.logfile, "getbof(): op=0x%04x version2=0x%04x streamtype=0x%04x" \ - % (opcode, version2, streamtype) + print("getbof(): op=0x%04x version2=0x%04x streamtype=0x%04x" + % (opcode, version2, streamtype), file=self.logfile) bof_offset = self._position - 4 - length if DEBUG: - print >> self.logfile, "getbof(): BOF found at offset %d; savpos=%d" \ - % (bof_offset, savpos) + print("getbof(): BOF found at offset %d; savpos=%d" + % (bof_offset, savpos), file=self.logfile) version = build = year = 0 if version1 == 0x08: build, year = unpack('= 2: - print >> self.logfile, \ - "BOF: op=0x%04x vers=0x%04x stream=0x%04x buildid=%d buildyr=%d -> BIFF%d" \ - % (opcode, version2, streamtype, build, year, version) + 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: @@ -1313,9 +1341,9 @@ def bof_error(msg): 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' \ + '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 @@ -1339,7 +1367,7 @@ def expand_cell_address(inrow, incol): def colname(colx, _A2Z="ABCDEFGHIJKLMNOPQRSTUVWXYZ"): assert colx >= 0 - name = u'' + name = UNICODE_LITERAL('') while 1: quot, rem = divmod(colx, 26) name = _A2Z[rem] + name @@ -1385,7 +1413,7 @@ def unpack_SST_table(datatab, nstrings): if options & 0x04: # phonetic phosz = local_unpack('= datalen: # adjust to correct position in next record diff --git a/xlrd/compdoc.py b/xlrd/compdoc.py index bbae0d22..412a89e7 100644 --- a/xlrd/compdoc.py +++ b/xlrd/compdoc.py @@ -1,29 +1,24 @@ -# -*- coding: cp1252 -*- +# -*- 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 -## -# Implements the minimal functionality required -# to extract a "Workbook" or "Book" stream (as one big string) -# from an OLE2 Compound Document file. -#

Copyright � 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. - -# 2008-11-04 SJM Avoid assertion error when -1 used instead of -2 for first_SID of empty SCSS [Frank Hoffsuemmer] -# 2007-09-08 SJM Warning message if sector sizes are extremely large. -# 2007-05-07 SJM Meaningful exception instead of IndexError if a SAT (sector allocation table) is corrupted. -# 2007-04-22 SJM Missing "<" in a struct.unpack call => can't open files on bigendian platforms. - -from __future__ import nested_scopes +import array import sys from struct import unpack -from timemachine import * -import array -## -# Magic cookie that should appear in the first 8 bytes of the file. -SIGNATURE = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" +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 @@ -46,7 +41,7 @@ def __init__(self, DID, dent, DEBUG=0, logfile=sys.stdout): (self.first_SID, self.tot_size) = \ unpack('> self.logfile, "DID=%d name=%r etype=%d DIDs(left=%d right=%d root=%d parent=%d kids=%r) first_SID=%d tot_size=%d" \ - % (self.DID, self.name, self.etype, self.left_DID, - self.right_DID, self.root_DID, self.parent, self.children, self.first_SID, self.tot_size) + fprintf( + self.logfile, + "DID=%d name=%r etype=%d DIDs(left=%d right=%d root=%d parent=%d kids=%r) first_SID=%d tot_size=%d\n", + self.DID, self.name, self.etype, self.left_DID, + self.right_DID, self.root_DID, self.parent, self.children, self.first_SID, self.tot_size + ) if DEBUG == 2: # cre_lo, cre_hi, mod_lo, mod_hi = tsinfo - print >> self.logfile, "timestamp info", self.tsinfo + print("timestamp info", self.tsinfo, file=self.logfile) def _build_family_tree(dirlist, parent_DID, child_DID): if child_DID < 0: return @@ -72,73 +70,74 @@ def _build_family_tree(dirlist, parent_DID, child_DID): if dirlist[child_DID].etype == 1: # storage _build_family_tree(dirlist, child_DID, dirlist[child_DID].root_DID) -## -# Compound document handler. -# @param mem The raw contents of the file, as a string, or as an mmap.mmap() object. The -# only operation it needs to support is slicing. class CompDoc(object): + """ + Compound document handler. + + :param mem: + The raw contents of the file, as a string, or as an :class:`mmap.mmap` + object. The only operation it needs to support is slicing. + """ - def __init__(self, mem, logfile=sys.stdout, DEBUG=0): + + def __init__(self, mem, logfile=sys.stdout, DEBUG=0, ignore_workbook_corruption=False): self.logfile = logfile + self.ignore_workbook_corruption = ignore_workbook_corruption self.DEBUG = DEBUG if mem[0:8] != SIGNATURE: raise CompDocError('Not an OLE2 compound document') - if mem[28:30] != '\xFE\xFF': + if mem[28:30] != b'\xFE\xFF': raise CompDocError('Expected "little-endian" marker, found %r' % mem[28:30]) revision, version = unpack('> logfile, "\nCompDoc format: version=0x%04x revision=0x%04x" % (version, revision) + print("\nCompDoc format: version=0x%04x revision=0x%04x" % (version, revision), file=logfile) self.mem = mem ssz, sssz = unpack(' 20: # allows for 2**20 bytes i.e. 1MB - print >> logfile, \ - "WARNING: sector size (2**%d) is preposterous; assuming 512 and continuing ..." \ - % ssz + print("WARNING: sector size (2**%d) is preposterous; assuming 512 and continuing ..." + % ssz, file=logfile) ssz = 9 if sssz > ssz: - print >> logfile, \ - "WARNING: short stream sector size (2**%d) is preposterous; assuming 64 and continuing ..." \ - % sssz + 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 >> logfile, "@@@@ sec_size=%d short_sec_size=%d" % (self.sec_size, self.short_sec_size) + 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('> logfile, \ - "WARNING *** file size (%d) not 512 + multiple of sector size (%d)" \ - % (len(mem), sec_size) + print("WARNING *** file size (%d) not 512 + multiple of sector size (%d)" + % (len(mem), sec_size), file=logfile) self.mem_data_secs = mem_data_secs # use for checking later self.mem_data_len = mem_data_len seen = self.seen = array.array('B', [0]) * mem_data_secs if DEBUG: - print >> logfile, 'sec sizes', ssz, sssz, sec_size, self.short_sec_size - print >> logfile, "mem data: %d bytes == %d sectors" % (mem_data_len, mem_data_secs) - print >> logfile, "SAT_tot_secs=%d, dir_first_sec_sid=%d, min_size_std_stream=%d" \ - % (SAT_tot_secs, self.dir_first_sec_sid, self.min_size_std_stream,) - print >> logfile, "SSAT_first_sec_sid=%d, SSAT_tot_secs=%d" % (SSAT_first_sec_sid, SSAT_tot_secs,) - print >> logfile, "MSATX_first_sec_sid=%d, MSATX_tot_secs=%d" % (MSATX_first_sec_sid, MSATX_tot_secs,) - nent = int_floor_div(sec_size, 4) # number of SID entries in a sector + print('sec sizes', ssz, sssz, sec_size, self.short_sec_size, file=logfile) + print("mem data: %d bytes == %d sectors" % (mem_data_len, mem_data_secs), file=logfile) + print("SAT_tot_secs=%d, dir_first_sec_sid=%d, min_size_std_stream=%d" + % (SAT_tot_secs, self.dir_first_sec_sid, self.min_size_std_stream,), file=logfile) + print("SSAT_first_sec_sid=%d, SSAT_tot_secs=%d" % (SSAT_first_sec_sid, SSAT_tot_secs,), file=logfile) + print("MSATX_first_sec_sid=%d, MSATX_tot_secs=%d" % (MSATX_first_sec_sid, MSATX_tot_secs,), file=logfile) + nent = sec_size // 4 # number of SID entries in a sector fmt = "<%di" % nent trunc_warned = 0 # # === build the MSAT === # MSAT = list(unpack('<109i', mem[76:512])) - SAT_sectors_reqd = int_floor_div(mem_data_secs + nent - 1, nent) - expected_MSATX_sectors = max(0, int_floor_div(SAT_sectors_reqd - 109 + nent - 2, nent - 1)) + SAT_sectors_reqd = (mem_data_secs + nent - 1) // nent + expected_MSATX_sectors = max(0, (SAT_sectors_reqd - 109 + nent - 2) // (nent - 1)) actual_MSATX_sectors = 0 if MSATX_tot_secs == 0 and MSATX_first_sec_sid in (EOCSID, FREESID, 0): # Strictly, if there is no MSAT extension, then MSATX_first_sec_sid @@ -146,16 +145,16 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): pass # Presuming no extension else: sid = MSATX_first_sec_sid - while sid not in (EOCSID, FREESID): + while sid not in (EOCSID, FREESID, MSATSID): # Above should be only EOCSID according to MS & OOo docs # but Excel doesn't complain about FREESID. Zero is a valid # sector number, not a sentinel. if DEBUG > 1: - print >> logfile, 'MSATX: sid=%d (0x%08X)' % (sid, sid) + 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 >> logfile, msg + print(msg, file=logfile) break raise CompDocError(msg) elif sid < 0: @@ -165,15 +164,15 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): seen[sid] = 1 actual_MSATX_sectors += 1 if DEBUG and actual_MSATX_sectors > expected_MSATX_sectors: - print >> logfile, "[1]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_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 >> logfile, "[2]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors + print("[2]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, file=logfile) if DEBUG: - print >> logfile, "MSAT: len =", len(MSAT) + print("MSAT: len =", len(MSAT), file=logfile) dump_list(MSAT, 10, logfile) # # === build the SAT === @@ -189,10 +188,9 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): continue if msid >= mem_data_secs: if not trunc_warned: - print >> logfile, "WARNING *** File is truncated, or OLE2 MSAT is corrupt!!" - print >> logfile, \ - "INFO: Trying to access sector %d but only %d available" \ - % (msid, mem_data_secs) + 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 @@ -204,24 +202,24 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): seen[msid] = 2 actual_SAT_sectors += 1 if DEBUG and actual_SAT_sectors > SAT_sectors_reqd: - print >> logfile, "[3]===>>>", mem_data_secs, nent, SAT_sectors_reqd, expected_MSATX_sectors, actual_MSATX_sectors, actual_SAT_sectors, msid + 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 >> logfile, "SAT: len =", len(self.SAT) + 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 >> logfile + # 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 >> logfile, "MSAT: len =", len(MSAT) + 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 >> logfile, "SAT: len =", len(self.SAT) + print("SAT: len =", len(self.SAT), file=logfile) dump_list(self.SAT, 10, logfile) # # === build the directory === @@ -262,8 +260,7 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): # self.SSAT = [] if SSAT_tot_secs > 0 and sscs_dir.tot_size == 0: - print >> logfile, \ - "WARNING *** OLE2 inconsistency: SSCS size is 0 but SSAT size is non-zero" + 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 @@ -276,13 +273,13 @@ def __init__(self, mem, logfile=sys.stdout, DEBUG=0): news = list(unpack(fmt, mem[start_pos:start_pos+sec_size])) self.SSAT.extend(news) sid = self.SAT[sid] - if DEBUG: print >> logfile, "SSAT last sid %d; remaining sectors %d" % (sid, nsecs) + if DEBUG: print("SSAT last sid %d; remaining sectors %d" % (sid, nsecs), file=logfile) assert nsecs == 0 and sid == EOCSID if DEBUG: - print >> logfile, "SSAT" + print("SSAT", file=logfile) dump_list(self.SSAT, 10, logfile) if DEBUG: - print >> logfile, "seen" + 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): @@ -304,7 +301,7 @@ def _get_stream(self, mem, base, sat, sec_size, start_sid, size=None, name='', s raise CompDocError( "OLE2 stream %r: sector allocation table invalid entry (%d)" % (name, s) - ) + ) assert s == EOCSID else: todo = size @@ -325,15 +322,14 @@ def _get_stream(self, mem, base, sat, sec_size, start_sid, size=None, name='', s raise CompDocError( "OLE2 stream %r: sector allocation table invalid entry (%d)" % (name, s) - ) + ) assert s == EOCSID if todo != 0: - print >> self.logfile, \ - "WARNING *** OLE2 stream %r: expected size %d, actual size %d" \ - % (name, size, size - todo) - # print >> self.logfile, "_get_stream(%s): seen" % name; dump_list(self.seen, 20, self.logfile) + fprintf(self.logfile, + "WARNING *** OLE2 stream %r: expected size %d, actual size %d\n", + name, size, size - todo) - return ''.join(sectors) + return b''.join(sectors) def _dir_search(self, path, storage_DID=0): # Return matching DirNode instance, or None @@ -353,12 +349,16 @@ def _dir_search(self, path, storage_DID=0): raise CompDocError("Requested stream is not a 'user stream'") return None - ## - # 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. u'Workbook'. Should be in Unicode or convertible thereto. 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 @@ -371,16 +371,23 @@ def get_named_stream(self, qname): self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID, d.tot_size, name=qname + " (from SSCS)", seen_id=None) - ## - # 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. u'Workbook'. Should be in Unicode or convertible thereto. - 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) @@ -389,10 +396,10 @@ def locate_named_stream(self, qname): % (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, + self.mem, 512, self.SAT, self.sec_size, d.first_SID, d.tot_size, qname, d.DID+6) if self.DEBUG: - print >> self.logfile, "\nseen" + print("\nseen", file=self.logfile) dump_list(self.seen, 20, self.logfile) return result else: @@ -401,8 +408,8 @@ def locate_named_stream(self, qname): self.SSCS, 0, self.SSAT, self.short_sec_size, d.first_SID, d.tot_size, qname + " (from SSCS)", None), 0, - d.tot_size - ) + 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 @@ -414,18 +421,20 @@ def _locate_stream(self, mem, base, sat, sec_size, start_sid, expected_stream_si end_pos = -8888 slices = [] tot_found = 0 - found_limit = int_floor_div(expected_stream_size + sec_size - 1, sec_size) + found_limit = (expected_stream_size + sec_size - 1) // sec_size while s >= 0: if self.seen[s]: - print >> self.logfile, "_locate_stream(%s): seen" % qname; dump_list(self.seen, 20, self.logfile) - raise CompDocError("%s corruption: seen[%d] == %d" % (qname, s, 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) - ) # Note: expected size rounded up to higher sector + ) if s == p+1: # contiguous sectors end_pos += sec_size @@ -446,21 +455,21 @@ def _locate_stream(self, mem, base, sat, sec_size, start_sid, expected_stream_si return (mem, start_pos, expected_stream_size) slices.append((start_pos, end_pos)) # print >> self.logfile, "+++>>> %d fragments" % len(slices) - return (''.join([mem[start_pos:end_pos] for start_pos, end_pos in slices]), 0, expected_stream_size) + 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 >> f, "%5d%s" % (dpos, " ="[equal]), + print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f) for value in alist[dpos:dpos + stride]: - print >> f, str(value), - print >> f + print(str(value), end=' ', file=f) + print(file=f) def dump_list(alist, stride, f=sys.stdout): def _dump_line(dpos, equal=0): - print >> f, "%5d%s" % (dpos, " ="[equal]), + print("%5d%s" % (dpos, " ="[equal]), end=' ', file=f) for value in alist[dpos:dpos + stride]: - print >> f, str(value), - print >> f + print(str(value), end=' ', file=f) + print(file=f) pos = None oldpos = None for pos in xrange(0, len(alist), stride): diff --git a/xlrd/doc/compdoc.html b/xlrd/doc/compdoc.html deleted file mode 100644 index 8a1e1731..00000000 --- a/xlrd/doc/compdoc.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - -The compdoc Module - - -

The compdoc Module

-

Implements the minimal functionality required -to extract a "Workbook" or "Book" stream (as one big string) -from an OLE2 Compound Document file. -

Copyright © 2005-2012 Stephen John Machin, Lingfo Pty Ltd

-

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

-

Module Contents

-
-
CompDoc(mem, logfile=sys.stdout, DEBUG=0) (class) [#]
-
-

Compound document handler.

-
-
mem
-
-The raw contents of the file, as a string, or as an mmap.mmap() object. The -only operation it needs to support is slicing.
-

-

For more information about this class, see The CompDoc Class.

-
-
SIGNATURE (variable) [#]
-
-

Magic cookie that should appear in the first 8 bytes of the file.

-
-
-

The CompDoc Class

-
-
CompDoc(mem, logfile=sys.stdout, DEBUG=0) (class) [#]
-
-

Compound document handler.

-
-
mem
-
-The raw contents of the file, as a string, or as an mmap.mmap() object. The -only operation it needs to support is slicing.
-

-
-
get_named_stream(qname) [#]
-
-

Interrogate the compound document's directory; return the stream as a string if found, otherwise -return None.

-
-
qname
-
-Name of the desired stream e.g. u'Workbook'. Should be in Unicode or convertible thereto.
-

-
-
locate_named_stream(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.

-
-
qname
-
-Name of the desired stream e.g. u'Workbook'. Should be in Unicode or convertible thereto.
-

-
-
- diff --git a/xlrd/doc/xlrd.html b/xlrd/doc/xlrd.html deleted file mode 100644 index ea061580..00000000 --- a/xlrd/doc/xlrd.html +++ /dev/null @@ -1,2071 +0,0 @@ - - - - -The xlrd Module - - -

The xlrd Module

-

A Python module for extracting data from MS Excel (TM) spreadsheet files. -

-Version 0.7.4 -- April 2012 -

- -

General information

- -

Acknowledgements

- -

-Development of this module 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. -

- -

Backporting to Python 2.1 was partially funded by - - Journyx - provider of timesheet and project accounting solutions. - -

- -

Provision of formatting information in version 0.6.1 was funded by - - Simplistix Ltd. - -

- -

Unicode

- -

This module presents all text strings as Python unicode objects. -From Excel 97 onwards, text in Excel spreadsheets has been stored as Unicode. -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 UnicodeDecodeError exception will be raised and -you will need to determine the encoding yourself, and tell xlrd: -

-    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: -

-    runxlrd.py -e koi8_r 3rows myfile.xls
-

-

The first place to look for an encoding ("codec name") is - -the Python documentation. -

-
- -

Dates in Excel spreadsheets

- -

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 -(a) the "number format" applied to them in Excel and/or -(b) knowing which cells are supposed to have dates in them. -This module helps with (a) 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 -module's xldate_as_tuple function to convert numbers from a workbook, -you must use the datemode attribute of the 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: -http://support.microsoft.com/default.aspx?scid=KB;EN-US;q180162

- - -

(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. 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 -- you'll see 1900-02-28 displayed.

- -

Reference: http://support.microsoft.com/default.aspx?scid=kb;en-us;214326

- -

(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 xldate_* functions in this module -take the view that such a number is a calendar-independent time of day (like Python's 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 xldate_* functions.

- -

Named references, constants, formulas, and macros

- -

-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 Book object provides two dictionaries (name_map and -name_and_scope_map) and a list (name_obj_list) which allow various -ways of accessing the Name objects. There is one Name object for -each NAME record found in the workbook. 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", -Name.cell(), which extracts the value in the case where the name -refers to a single cell. More convenience methods are planned. The -source code for Name.cell (in __init__.py) is an extra source of -information on how the Name attributes hang together. -

- -

Name information is not extracted from files older than -Excel 5.0 (Book.biff_version < 50)

- -

Formatting

- -

Introduction

- -

This collection of features, new in xlrd version 0.6.1, is intended -to provide the information needed to (1) display/render spreadsheet contents -(say) on a screen or in a PDF file, and (2) copy spreadsheet data to another -file without losing the ability to display/render it.

- -

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, Rowinfo class) is used if available. -Failing that, column default information (COLINFO record, 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 version 0.6.1

-
    -
  • Rich text i.e. strings containing partial bold italic - and underlined text, change of font inside a string, etc. - See OOo docs s3.4 and s3.2. - Rich text is included in version 0.7.2
  • -
  • 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 that this does not affect users who are copying XLS - files, only those who are visually rendering cells.
  • -
- -

Loading worksheets on demand

- -

This feature, new in version 0.7.1, is governed by the on_demand argument -to the 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. open_workbook() loads global data -and all sheets, releases resources no longer required (principally the -str or 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: open_workbook() loads global -data and returns without releasing resources. At this stage, the only -information available about sheets is Book.nsheets and Book.sheet_names().

- -

Book.sheet_by_name() and Book.sheet_by_index() will load the requested -sheet if it is not already loaded.

- -

Book.sheets() will load all/any unloaded sheets.

- -

The caller may save memory by calling -Book.unload_sheet(sheet_name_or_index) when finished with the sheet. -This applies irrespective of the state of on_demand.

- -

The caller may re-load an unloaded sheet by calling Book.sheet_by_xxxx() - -- except if those 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: -Book.sheet_loaded(sheet_name_or_index) -> a bool

- -

Book.release_resources() may used to save memory and close -any memory-mapped file before proceding 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 -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 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 Book.release_resources() -explicitly in the finally suite of a try/finally block. -New in xlrd 0.7.2: the Book object is a "context manager", so if -using Python 2.5 or later, you can wrap your code in a "with" -statement.

-

Module Contents

-
-
BaseObject (class) [#]
-
-

Parent of almost all other classes in the package.

-

For more information about this class, see The BaseObject Class.

-
-
Book() (class) [#]
-
-

Contents of a "workbook".

-

For more information about this class, see The Book Class.

-
-
Cell(ctype, value, xf_index=None) (class) [#]
-
-

Contains the data for one cell.

-

For more information about this class, see The Cell Class.

-
-
cellname(rowx, colx) [#]
-
-

Utility function: (5, 7) => 'H6'

-
-
cellnameabs(rowx, colx, r1c1=0) [#]
-
-

Utility function: (5, 7) => '$H$6'

-
-
Colinfo (class) [#]
-
-

Width and default formatting information that applies to one or -more columns in a sheet.

-

For more information about this class, see The Colinfo Class.

-
-
colname(colx) [#]
-
-

Utility function: 7 => 'H', 27 => 'AB'

-
-
count_records(filename, outfile=sys.stdout) [#]
-
-

For debugging and analysis: summarise the file's BIFF records. -I.e. produce a sorted file of (record_name, count).

-
-
filename
-
-The path to the file to be summarised.
-
outfile
-
-An open file, to which the summary is written.
-

-
-
dump(filename, outfile=sys.stdout, unnumbered=False) [#]
-
-

For debugging: dump the file's BIFF records in char & hex. -

-
filename
-
-The path to the file to be dumped.
-
outfile
-
-An open file, to which the dump is written.
-
unnumbered
-
-If true, omit offsets (for meaningful diffs).
-

-
-
empty_cell (variable) [#]
-
-

There is one and only one instance of an empty cell -- it's a singleton. This is it. -You may use a test like "acell is empty_cell".

-
-
EqNeAttrs (class) [#]
-
-

This mixin class exists solely so that Format, Font, and XF....

-

For more information about this class, see The EqNeAttrs Class.

-
-
error_text_from_code (variable) [#]
-
-

This dictionary can be used to produce a text version of the internal codes -that Excel uses for error cells. Here are its contents: -

-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
-

-
-
Font (class) [#]
-
-

An Excel "font" contains the details of not only what is normally -considered a font, but also several other display attributes.

-

For more information about this class, see The Font Class.

-
-
Format(format_key, ty, format_str) (class) [#]
-
-

"Number format" information from a FORMAT record.

-

For more information about this class, see The Format Class.

-
-
Hyperlink (class) [#]
-
-

Contains the attributes of a hyperlink.

-

For more information about this class, see The Hyperlink Class.

-
-
Name (class) [#]
-
-

Information relating to a named reference, formula, macro, etc.

-

For more information about this class, see The Name Class.

-
-
Note (class) [#]
-
-

Represents a user "comment" or "note".

-

For more information about this class, see The Note Class.

-
-
open_workbook(filename=None, -logfile=sys.stdout, verbosity=0, pickleable=True, use_mmap=USE_MMAP, -file_contents=None, -encoding_override=None, -formatting_info=False, on_demand=False, ragged_rows=False, -) [#]
-
-

Open a spreadsheet file for data extraction.

-
-
filename
-
-The path to the spreadsheet file to be opened.
-
logfile
-
-An open file to which messages and diagnostics are written.
-
verbosity
-
-Increases the volume of trace material written to the logfile.
-
pickleable
-
-Default is true. In Python 2.4 or earlier, setting to false -will cause use of array.array objects which save some memory but can't be pickled. -In Python 2.5, array.arrays are used unconditionally. Note: if you have large files that -you need to read multiple times, it can be much faster to cPickle.dump() the xlrd.Book object -once, and use cPickle.load() multiple times.
-
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.
-
file_contents
-
-... as a string or an mmap.mmap object or some other behave-alike object. -If file_contents is supplied, filename will not be used, except (possibly) in messages.
-
encoding_override
-
-Used to overcome missing or bad codepage information -in older-version files. Refer to discussion in the Unicode section above. -
-- New in version 0.6.0 - -
-
formatting_info
-
-Governs provision of a reference to an XF (eXtended Format) object -for each cell in the worksheet. -
Default is False. This is backwards compatible and saves memory. -"Blank" cells (those with their own formatting information but no data) are treated as empty -(by ignoring the file's BLANK and MULBLANK records). -It cuts off any bottom "margin" of rows of empty (and blank) cells and -any right "margin" of columns of empty (and blank) cells. -Only cell_value and cell_type are available. -
True provides all cells, including empty and blank cells. -XF information is available for each cell. -
-- New in version 0.6.1 - -
-
on_demand
-
-Governs whether sheets are all loaded initially or when demanded -by the caller. Please refer back to the section "Loading worksheets on demand" for details. -
-- New in version 0.7.1 - -
-
ragged_rows
-
-False (the default) means all rows are padded out with empty cells so that all -rows have the same size (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 -Sheet.row_len() method. -
-- New in version 0.7.2 - -
-
Returns:
-
-An instance of the Book class.
-

-
-
Operand(akind=None, avalue=None, arank=0, atext='?') (class) [#]
-
-

Used in evaluating formulas.

-

For more information about this class, see The Operand Class.

-
-
rangename3d(book, ref3d) [#]
-
-

Utility function: -
Ref3D((1, 4, 5, 20, 7, 10)) => 'Sheet2:Sheet3!$H$6:$J$20' -

-
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)) -R1C1 mode => 'Sheet1!R[-32]C[-13]:R[-23]C[12]' -A1 mode => depends on base cell (browx, bcolx) -

-
Ref3D(atuple) (class) [#]
-
-

Represents an absolute or relative 3-dimensional reference to a box -of one or more cells.

-

For more information about this class, see The Ref3D Class.

-
-
Rowinfo() (class) [#]
-
-

Height and default formatting information that applies to a row in a sheet.

-

For more information about this class, see The Rowinfo Class.

-
-
Sheet(book, position, name, number) (class) [#]
-
-

Contains the data for one worksheet.

-

For more information about this class, see The Sheet Class.

-
-
XF (class) [#]
-
-

eXtended Formatting information for cells, rows, columns and styles.

-

For more information about this class, see The XF Class.

-
-
XFAlignment (class) [#]
-
-

A collection of the alignment and similar attributes of an XF record.

-

For more information about this class, see The XFAlignment Class.

-
-
XFBackground (class) [#]
-
-

A collection of the background-related attributes of an XF record.

-

For more information about this class, see The XFBackground Class.

-
-
XFBorder (class) [#]
-
-

A collection of the border-related attributes of an XF record.

-

For more information about this class, see The XFBorder Class.

-
-
XFProtection (class) [#]
-
-

A collection of the protection-related attributes of an XF record.

-

For more information about this class, see The XFProtection Class.

-
-
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.

-
-
xldate
-
-The Excel number
-
datemode
-
-0: 1900-based, 1: 1904-based. -
WARNING: when using this function to -interpret the contents of a workbook, you should pass in the Book.datemode -attribute of that workbook. Whether -the workbook has ever been anywhere near a Macintosh is irrelevant. -
-
Returns:
-
-Gregorian (year, month, day, hour, minute, nearest_second). -
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. -
-
Raises XLDateNegative:
-xldate < 0.00 -
-
Raises XLDateAmbiguous:
-The 1900 leap-year problem (datemode == 0 and 1.0 <= xldate < 61.0) -
-
Raises XLDateTooLarge:
-Gregorian year 10000 or later
-
Raises XLDateBadDatemode:
-datemode arg is neither 0 nor 1
-
Raises XLDateError:
-Covers the 4 specific errors
-

-
-
xldate_from_date_tuple((year, month, day), datemode) [#]
-
-

Convert a date tuple (year, month, day) to an Excel date.

-
-
year
-
-Gregorian year.
-
month
-
-1 <= month <= 12 -
-
day
-
-1 <= day <= last day of that (year, month) -
-
datemode
-
-0: 1900-based, 1: 1904-based.
-
Raises XLDateAmbiguous:
-The 1900 leap-year problem (datemode == 0 and 1.0 <= xldate < 61.0) -
-
Raises XLDateBadDatemode:
-datemode arg is neither 0 nor 1
-
Raises XLDateBadTuple:
-(year, month, day) is too early/late or has invalid component(s)
-
Raises XLDateError:
-Covers the specific errors
-

-
-
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.

-
-
datetime_tuple
-
-(year, month, day, hour, minute, second)
-
datemode
-
-0: 1900-based, 1: 1904-based.
-

-
-
xldate_from_time_tuple((hour, minute, second)) [#]
-
-

Convert a time tuple (hour, minute, second) to an Excel "date" value (fraction of a day).

-
-
hour
-
-0 <= hour < 24 -
-
minute
-
-0 <= minute < 60 -
-
second
-
-0 <= second < 60 -
-
Raises XLDateBadTuple:
-Out-of-range hour, minute, or second
-

-
-
-

The BaseObject Class

-
-
BaseObject (class) [#]
-
-

Parent of almost all other classes in the package. Defines a common "dump" method -for debugging.

-
-
dump(f=None, header=None, footer=None, indent=0) [#]
-
-
-
f
-
-open file object, to which the dump is written
-
header
-
-text to write before the dump
-
footer
-
-text to write after the dump
-
indent
-
-number of leading spaces (for recursive calls)
-

-
-
-

The Book Class

-
-
Book() (class) [#]
-
-

Contents of a "workbook". -

WARNING: You don't call this class yourself. You use the Book object that -was returned when you called xlrd.open_workbook("myfile.xls").

-
-
biff_version [#]
-
-

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).

-
-
codepage [#]
-
-

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'

-
-
colour_map [#]
-
-

This provides definitions for colour indexes. Please refer to the -above section "The Palette; Colour Indexes" 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. -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 palette_record. -
-- New in version 0.6.1. Extracted only if open_workbook(..., formatting_info=True) -

-
countries [#]
-
-

A tuple containing the (telephone system) 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. -

-
datemode [#]
-
-

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).
-

-
encoding [#]
-
-

The encoding that was derived from the codepage.

-
-
font_list [#]
-
-

A list of Font class instances, each corresponding to a FONT record. -
-- New in version 0.6.1 -

-
format_list [#]
-
-

A list of 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) pyExcelerator, -use this list. -The collection to be used for all visual rendering purposes is format_map. -
-- New in version 0.6.1 -

-
format_map [#]
-
-

The mapping from XF.format_key to Format object. -
-- New in version 0.6.1 -

-
load_time_stage_1 [#]
-
-

Time in seconds to extract the XLS image as a contiguous string (or mmap equivalent).

-
-
load_time_stage_2 [#]
-
-

Time in seconds to parse the data from the contiguous string (or mmap equivalent).

-
-
name_and_scope_map [#]
-
-

A mapping from (lower_case_name, scope) to a single Name object. -
-- New in version 0.6.0 -

-
name_map [#]
-
-

A mapping from lower_case_name to a list of Name objects. The list is -sorted in scope order. Typically there will be one item (of global scope) -in the list. -
-- New in version 0.6.0 -

-
name_obj_list [#]
-
-

List containing a Name object for each NAME record in the workbook. -
-- New in version 0.6.0 -

-
nsheets [#]
-
-

The number of worksheets present in the workbook file. -This information is available even when no sheets have yet been loaded.

-
-
palette_record [#]
-
-

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 colour_map. -
-- New in version 0.6.1. Extracted only if open_workbook(..., formatting_info=True) -

-
release_resources() [#]
-
-

This method has a dual purpose. You can call it to release -memory-consuming objects and (possibly) a memory-mapped file -(mmap.mmap object) when you have finished loading sheets in -on_demand mode, but still require the Book object to examine the -loaded sheets. It is also called automatically (a) when 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.

-
-
sheet_by_index(sheetx) [#]
-
-
-
sheetx
-
-Sheet index in range(nsheets)
-
Returns:
-
-An object of the Sheet class
-

-
-
sheet_by_name(sheet_name) [#]
-
-
-
sheet_name
-
-Name of sheet required
-
Returns:
-
-An object of the Sheet class
-

-
-
sheet_loaded(sheet_name_or_index) [#]
-
-
-
sheet_name_or_index
-
-Name or index of sheet enquired upon
-
Returns:
-
-true if sheet is loaded, false otherwise -
-- New in version 0.7.1 -
-

-
-
sheet_names() [#]
-
-
-
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.
-

-
-
sheets() [#]
-
-
-
Returns:
-
-A list of all sheets in the book. -All sheets not already loaded will be loaded.
-

-
-
style_name_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 1 = built-in style, 0 = user-defined
-xf_index is an index into Book.xf_list.
-References: OOo docs s6.99 (STYLE record); Excel UI Format/Style -
-- New in version 0.6.1; since 0.7.4, extracted only if -open_workbook(..., formatting_info=True) -

-
unload_sheet(sheet_name_or_index) [#]
-
-
-
sheet_name_or_index
-
-Name or index of sheet to be unloaded. -
-- New in version 0.7.1 -
-

-
-
user_name [#]
-
-

What (if anything) is recorded as the name of the last user to save the file.

-
-
xf_list [#]
-
-

A list of XF class instances, each corresponding to an XF record. -
-- New in version 0.6.1 -

-
-

The Cell Class

-
-
Cell(ctype, value, xf_index=None) (class) [#]
-
-

Contains the data for one cell.

- -

WARNING: You don't call this class yourself. You access Cell objects -via methods of the Sheet object(s) that you found in the Book object that -was returned when you called xlrd.open_workbook("myfile.xls").

-

Cell objects have three attributes: ctype is an int, value -(which depends on ctype) and xf_index. -If "formatting_info" is not enabled when the workbook is opened, xf_index will be None. -The following table describes the types of cells and how their values -are represented in Python.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Type symbolType numberPython value
XL_CELL_EMPTY0empty string u''
XL_CELL_TEXT1a Unicode string
XL_CELL_NUMBER2float
XL_CELL_DATE3float
XL_CELL_BOOLEAN4int; 1 means TRUE, 0 means FALSE
XL_CELL_ERROR5int representing internal Excel codes; for a text representation, -refer to the supplied dictionary error_text_from_code
XL_CELL_BLANK6empty string u''. Note: this type will appear only when -open_workbook(..., formatting_info=True) is used.
-

-

-
-

The Colinfo Class

-
-
Colinfo (class) [#]
-
-

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 [see footnote!]). - -
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.""" -
- -Footnote: 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. -
"""
-At the moment (2007-01-17) xlrd is going with the GCW version of the story. -Reference to the source may be useful: see the computed_column_width(colx) method -of the Sheet class. -
-- New in version 0.6.1 -

-
-
bit1_flag [#]
-
-

Value of a 1-bit flag whose purpose is unknown -but is often seen set to 1

-
-
collapsed [#]
-
-

1 = column is collapsed

-
-
hidden [#]
-
-

1 = column is hidden

-
-
outline_level [#]
-
-

Outline level of the column, in range(7). -(0 = no outline)

-
-
width [#]
-
-

Width of the column in 1/256 of the width of the zero character, -using default font (first FONT record in the file).

-
-
xf_index [#]
-
-

XF index to be used for formatting empty cells.

-
-
-

The EqNeAttrs Class

-
-
EqNeAttrs (class) [#]
-
-

This mixin class exists solely so that Format, Font, and XF.... objects -can be compared by value of their attributes.

-
-
-

The Font Class

-
-
Font (class) [#]
-
-

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. -
-- New in version 0.6.1 -

-
bold [#]
-
-

1 = Characters are bold. Redundant; see "weight" attribute.

-
-
character_set [#]
-
-

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

-
-
colour_index [#]
-
-

An explanation of "colour index" is given in the Formatting -section at the start of this document.

-
-
escapement [#]
-
-

1 = Superscript, 2 = Subscript.

-
-
family [#]
-
-

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) -

-
font_index [#]
-
-

The 0-based index used to refer to this Font() instance. -Note that index 4 is never used; xlrd supplies a dummy place-holder.

-
-
height [#]
-
-

Height of the font (in twips). A twip = 1/20 of a point.

-
-
italic [#]
-
-

1 = Characters are italic.

-
-
name [#]
-
-

The name of the font. Example: u"Arial"

-
-
outline [#]
-
-

1 = Font is outline style (Macintosh only)

-
-
shadow [#]
-
-

1 = Font is shadow style (Macintosh only)

-
-
struck_out [#]
-
-

1 = Characters are struck out.

-
-
underline_type [#]
-
-

0 = None
-1 = Single; 0x21 (33) = Single accounting
-2 = Double; 0x22 (34) = Double accounting -

-
underlined [#]
-
-

1 = Characters are underlined. Redundant; see "underline_type" attribute.

-
-
weight [#]
-
-

Font weight (100-1000). Standard values are 400 for normal text -and 700 for bold text.

-
-
-

The Format Class

-
-
Format(format_key, ty, format_str) (class) [#]
-
-

"Number format" information from a FORMAT record. -
-- New in version 0.6.1 -

-
format_key [#]
-
-

The key into Book.format_map

-
-
format_str [#]
-
-

The format string

-
-
type [#]
-
-

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 -

-
-

The Hyperlink Class

-
-
Hyperlink (class) [#]
-
-

Contains the attributes of a hyperlink. -Hyperlink objects are accessible through Sheet.hyperlink_list -and Sheet.hyperlink_map. -
-- New in version 0.7.2 -

-
-
desc [#]
-
-

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.

-
-
fcolx [#]
-
-

Index of first column

-
-
frowx [#]
-
-

Index of first row

-
-
lcolx [#]
-
-

Index of last column

-
-
lrowx [#]
-
-

Index of last row

-
-
quicktip [#]
-
-

The text of the "quick tip" displayed when the cursor -hovers over the hyperlink.

-
-
target [#]
-
-

Target frame. Unicode string. Note: I have not seen a case of this. -It seems impossible to create one in the Excel UI.

-
-
textmark [#]
-
-

"Textmark": the piece after the "#" in -"http://docs.python.org/library#struct_module", or the Sheet1!A1:Z99 -part when type is "workbook".

-
-
type [#]
-
-

Type of hyperlink. Unicode string, one of 'url', 'unc', -'local file', 'workbook', 'unknown'

-
-
url_or_path [#]
-
-

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 bytes (3.x) or str (2.x) string, with unknown encoding. -

-
-

The Name Class

-
-
Name (class) [#]
-
-

Information relating to a named reference, formula, macro, etc. -
-- New in version 0.6.0 -
-- Name information is not extracted from files older than -Excel 5.0 (Book.biff_version < 50) -

-
area2d(clipped=True) [#]
-
-

This is a convenience method for the use case where the name -refers to one rectangular area in one worksheet.

-
-
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 XLRDError:
-The name is not a constant absolute reference -to a single area in a single sheet.
-

-
-
binary [#]
-
-

0 = Formula definition; 1 = Binary data
No examples have been sighted. -

-
builtin [#]
-
-

0 = User-defined name; 1 = Built-in name -(common examples: Print_Area, Print_Titles; see OOo docs for full list)

-
-
cell() [#]
-
-

This is a convenience method for the frequent use case where the name -refers to a single cell.

-
-
Returns:
-
-An instance of the Cell class.
-
Raises XLRDError:
-The name is not a constant absolute reference -to a single cell.
-

-
-
complex [#]
-
-

0 = Simple formula; 1 = Complex formula (array formula or user defined)
-No examples have been sighted. -

-
func [#]
-
-

0 = Command macro; 1 = Function macro. Relevant only if macro == 1

-
-
funcgroup [#]
-
-

Function group. Relevant only if macro == 1; see OOo docs for values.

-
-
hidden [#]
-
-

0 = Visible; 1 = Hidden

-
-
macro [#]
-
-

0 = Standard name; 1 = Macro name

-
-
name [#]
-
-

A Unicode string. If builtin, decoded as per OOo docs.

-
-
name_index [#]
-
-

The index of this object in book.name_obj_list

-
-
raw_formula [#]
-
-

An 8-bit string.

-
-
result [#]
-
-

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 -Operand class.

-
-
scope [#]
-
-

-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. -

-
vbasic [#]
-
-

0 = Sheet macro; 1 = VisualBasic macro. Relevant only if macro == 1

-
-
-

The Note Class

-
-
Note (class) [#]
-
-

Represents a user "comment" or "note". -Note objects are accessible through Sheet.cell_note_map. -
-- New in version 0.7.2 -

-
-
author [#]
-
-

Author of note

-
-
col_hidden [#]
-
-

True if the containing column is hidden

-
-
colx [#]
-
-

Column index

-
-
rich_text_runlist [#]
-
-

List of (offset_in_string, font_index) tuples. -Unlike Sheet.rich_text_runlist_map, the first offset should always be 0. -

-
row_hidden [#]
-
-

True if the containing row is hidden

-
-
rowx [#]
-
-

Row index

-
-
show [#]
-
-

True if note is always shown

-
-
text [#]
-
-

Text of the note

-
-
-

The Operand Class

-
-
Operand(akind=None, avalue=None, arank=0, atext='?') (class) [#]
-
-

Used in evaluating formulas. -The following table describes the kinds and how their values -are represented.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Kind symbolKind numberValue representation
oBOOL3integer: 0 => False; 1 => True
oERR4None, or an int error code (same as XL_CELL_ERROR in the Cell class). -
oMSNG5Used by Excel as a placeholder for a missing (not supplied) function -argument. Should *not* appear as a final formula result. Value is None.
oNUM2A float. Note that there is no way of distinguishing dates.
oREF-1The value is either None or a non-empty list of -absolute Ref3D instances.
-
oREL-2The value is None or a non-empty list of -fully or partially relative Ref3D instances. -
oSTRG1A Unicode string.
oUNK0The kind is unknown or ambiguous. The value is None
-

-

-
kind [#]
-
-

oUNK means that the kind of operand is not known unambiguously.

-
-
text [#]
-
-

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.

-
-
value [#]
-
-

None means that the actual value of the operand is a variable -(depends on cell data), not a constant.

-
-
-

The Ref3D Class

-
-
Ref3D(atuple) (class) [#]
-
-

Represents an absolute or relative 3-dimensional reference to a box -of one or more cells.
--- New in version 0.6.0 -

- -

The coords attribute is a tuple of the form:
-(shtxlo, shtxhi, rowxlo, rowxhi, colxlo, colxhi)
-where 0 <= thingxlo <= thingx < thingxhi.
-Note that 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: 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 that 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 also that 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 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, ...). -

-
-

The Rowinfo Class

-
-
Rowinfo() (class) [#]
-
-

Height and default formatting information that applies to a row in a sheet. -Derived from ROW records. -
-- New in version 0.6.1

- -

height: Height of the row, in twips. One twip == 1/20 of a point.

- -

has_default_height: 0 = Row has custom height; 1 = Row has default height.

- -

outline_level: Outline level of the row (0 to 7)

- -

outline_group_starts_ends: 1 = Outline group starts or ends here (depending on where the -outline buttons are located, see WSBOOL record [TODO ??]), -and is collapsed

- -

hidden: 1 = Row is hidden (manually, or by a filter or outline group)

- -

height_mismatch: 1 = Row height and default font height do not match

- -

has_default_xf_index: 1 = the xf_index attribute is usable; 0 = ignore it

- -

xf_index: Index to default XF record for empty cells in this row. -Don't use this if has_default_xf_index == 0.

- -

additional_space_above: 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.

- -

additional_space_below: 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.

-
-
-

The Sheet Class

-
-
Sheet(book, position, name, number) (class) [#]
-
-

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 Cell class.

- -

WARNING: You don't call this class yourself. You access Sheet objects via the Book object that -was returned when you called xlrd.open_workbook("myfile.xls").

-
-
book [#]
-
-

A reference to the Book object to which this sheet belongs. -Example usage: some_sheet.book.datemode

-
-
cell(rowx, colx) [#]
-
-

Cell object in the given row and column. -

-
cell_note_map [#]
-
-

A sparse mapping from (rowx, colx) to a Note object. -Cells not containing a note ("comment") are not mapped. -
-- New in version 0.7.2

-
-
cell_type(rowx, colx) [#]
-
-

Type of the cell in the given row and column. -Refer to the documentation of the Cell class. -

-
cell_value(rowx, colx) [#]
-
-

Value of the cell in the given row and column.

-
-
cell_xf_index(rowx, colx) [#]
-
-

XF index of the cell in the given row and column. -This is an index into Book.xf_list. -
-- New in version 0.6.1 -

-
col(colx) [#]
-
-

Returns a sequence of the Cell objects in the given column. -

-
col_label_ranges [#]
-
-

List of address ranges of cells containing column labels. -These are set up in Excel by Insert > Name > Labels > Columns. -
-- New in version 0.6.0 -
How to deconstruct the list: -

-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_slice(colx, start_rowx=0, end_rowx=None) [#]
-
-

Returns a slice of the Cell objects in the given column. -

-
col_types(colx, start_rowx=0, end_rowx=None) [#]
-
-

Returns a slice of the types of the cells in the given column.

-
-
col_values(colx, start_rowx=0, end_rowx=None) [#]
-
-

Returns a slice of the values of the cells in the given column.

-
-
colinfo_map [#]
-
-

The map from a column index to a Colinfo object. Often there is an entry -in COLINFO records for all column indexes in range(257). -Note that xlrd ignores the entry for the non-existent -257th column. On the other hand, there may be no entry for unused columns. -
-- New in version 0.6.1. Populated only if open_workbook(formatting_info=True). -

-
computed_column_width(colx) [#]
-
-

Determine column display width. -
-- New in version 0.6.1 -
-

-
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).
-
Returns:
-
-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).
-

-
-
default_additional_space_above [#]
-
-

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 [#]
-
-

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 [#]
-
-

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 [#]
-
-

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 [#]
-
-

Default value to be used for a row if there is -no ROW record for that row. -From the optional DEFAULTROWHEIGHT record. -

-
defcolwidth [#]
-
-

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 Colinfo class. -
-- New in version 0.6.1 -

-
gcw [#]
-
-

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 Colinfo class for discussion. -

-
has_pane_record [#]
-
-

Boolean specifying if a PANE record was present, ignore unless you're xlutils.copy

-
-
horizontal_page_breaks [#]
-
-

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). -
-- New in version 0.7.2 -

-
horz_split_first_visible [#]
-
-

Index of first visible row in bottom frozen/split pane

-
-
horz_split_pos [#]
-
-

Number of rows in top pane (frozen panes; for split panes, see comments below in code)

-
-
hyperlink_list [#]
-
-

A list of Hyperlink objects corresponding to HLINK records found -in the worksheet.
-- New in version 0.7.2

-
-
hyperlink_map [#]
-
-

A sparse mapping from (rowx, colx) to an item in 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 hyperlink_list. -
-- New in version 0.7.2

-
-
merged_cells [#]
-
-

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. -
-- New in version 0.6.1. Extracted only if open_workbook(formatting_info=True). -
How to deconstruct the list: -

-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.
-
-

-
name [#]
-
-

Name of sheet.

-
-
ncols [#]
-
-

Nominal number of columns in sheet. It is 1 + the maximum column index -found, ignoring trailing empty cells. See also open_workbook(ragged_rows=?) -and Sheet.row_len(row_index). -

-
nrows [#]
-
-

Number of rows in sheet. A row index is in range(thesheet.nrows).

-
-
rich_text_runlist_map [#]
-
-

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: -

-runlist = thesheet.rich_text_runlist_map.get((rowx, colx))
-if runlist:
-    for offset, font_index in runlist:
-        # do work here.
-        pass
-
-Populated only if open_workbook(formatting_info=True). -
-- New in version 0.7.2. -
  -

-
row(rowx) [#]
-
-

Returns a sequence of the Cell objects in the given row. -

-
row_label_ranges [#]
-
-

List of address ranges of cells containing row labels. -For more details, see col_label_ranges above. -
-- New in version 0.6.0 -

-
row_len(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 ncols cells. -
-- New in version 0.7.2 -

-
row_slice(rowx, start_colx=0, end_colx=None) [#]
-
-

Returns a slice of the Cell objects in the given row. -

-
row_types(rowx, start_colx=0, end_colx=None) [#]
-
-

Returns a slice of the types -of the cells in the given row.

-
-
row_values(rowx, start_colx=0, end_colx=None) [#]
-
-

Returns a slice of the values -of the cells in the given row.

-
-
rowinfo_map [#]
-
-

The map from a row index to a Rowinfo object. Note that it is possible -to have missing entries -- at least one source of XLS files doesn't -bother writing ROW records. -
-- New in version 0.6.1. Populated only if open_workbook(formatting_info=True). -

-
split_active_pane [#]
-
-

Frozen panes: ignore it. Split panes: explanation and diagrams in OOo docs.

-
-
standardwidth [#]
-
-

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 Colinfo class. -
-- New in version 0.6.1 -

-
vert_split_first_visible [#]
-
-

Index of first visible column in right frozen/split pane

-
-
vert_split_pos [#]
-
-

Number of columns in left pane (frozen panes; for split panes, see comments below in code)

-
-
vertical_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). -
-- New in version 0.7.2 -

-
visibility [#]
-
-

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).

-
-
-

The XF Class

-
-
XF (class) [#]
-
-

eXtended Formatting information for cells, rows, columns and styles. -
-- New in version 0.6.1 - -

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 that the API -provides both "raw" XFs and "computed" XFs -- in the latter case, cell XFs -have had the above inheritance mechanism applied. -

-
-
_alignment_flag [#]
-
-
-
_background_flag [#]
-
-
-
_border_flag [#]
-
-
-
_font_flag [#]
-
-
-
_format_flag [#]
-
-
-
_protection_flag [#]
-
-

  -

-
alignment [#]
-
-

An instance of an XFAlignment object.

-
-
background [#]
-
-

An instance of an XFBackground object.

-
-
border [#]
-
-

An instance of an XFBorder object.

-
-
font_index [#]
-
-

Index into Book.font_list

-
-
format_key [#]
-
-

Key into 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. -

-
-
is_style [#]
-
-

0 = cell XF, 1 = style XF

-
-
parent_style_index [#]
-
-

cell XF: Index into Book.xf_list -of this XF's style XF
-style XF: 0xFFF -

-
protection [#]
-
-

An instance of an XFProtection object.

-
-
xf_index [#]
-
-

Index into Book.xf_list

-
-
-

The XFAlignment Class

-
-
XFAlignment (class) [#]
-
-

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. -
-- New in version 0.6.1 -

-
hor_align [#]
-
-

Values: section 6.115 (p 214) of OOo docs

-
-
indent_level [#]
-
-

A number in range(15).

-
-
rotation [#]
-
-

Values: section 6.115 (p 215) of OOo docs.
-Note: file versions BIFF7 and earlier use the documented -"orientation" attribute; this will be mapped (without loss) -into "rotation". -

-
shrink_to_fit [#]
-
-

1 = shrink font size to fit text into cell.

-
-
text_direction [#]
-
-

0 = according to context; 1 = left-to-right; 2 = right-to-left

-
-
text_wrapped [#]
-
-

1 = text is wrapped at right margin

-
-
vert_align [#]
-
-

Values: section 6.115 (p 215) of OOo docs

-
-
-

The XFBackground Class

-
-
XFBackground (class) [#]
-
-

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 explanation of "colour index" is given in the Formatting -section at the start of this document. -
-- New in version 0.6.1 -

-
background_colour_index [#]
-
-

See section 3.11 of the OOo docs.

-
-
fill_pattern [#]
-
-

See section 3.11 of the OOo docs.

-
-
pattern_colour_index [#]
-
-

See section 3.11 of the OOo docs.

-
-
-

The XFBorder Class

-
-
XFBorder (class) [#]
-
-

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 the Formatting -section at the start of this document. -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)".

-
-- New in version 0.6.1 -
-
bottom_colour_index [#]
-
-

The colour index for the cell's bottom line

-
-
bottom_line_style [#]
-
-

The line style for the cell's bottom line

-
-
diag_colour_index [#]
-
-

The colour index for the cell's diagonal lines, if any

-
-
diag_down [#]
-
-

1 = draw a diagonal from top left to bottom right

-
-
diag_line_style [#]
-
-

The line style for the cell's diagonal lines, if any

-
-
diag_up [#]
-
-

1 = draw a diagonal from bottom left to top right

-
-
left_colour_index [#]
-
-

The colour index for the cell's left line

-
-
left_line_style [#]
-
-

The line style for the cell's left line

-
-
right_colour_index [#]
-
-

The colour index for the cell's right line

-
-
right_line_style [#]
-
-

The line style for the cell's right line

-
-
top_colour_index [#]
-
-

The colour index for the cell's top line

-
-
top_line_style [#]
-
-

The line style for the cell's top line

-
-
-

The XFProtection Class

-
-
XFProtection (class) [#]
-
-

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. -
-- New in version 0.6.1 -

-
cell_locked [#]
-
-

1 = Cell is prevented from being changed, moved, resized, or deleted -(only if the sheet is protected).

-
-
formula_hidden [#]
-
-

1 = Hide formula so that it doesn't appear in the formula bar when -the cell is selected (only if the sheet is protected).

-
-
- diff --git a/xlrd/examples/xlrdnameAPIdemo.py b/xlrd/examples/xlrdnameAPIdemo.py deleted file mode 100644 index 6cd60df9..00000000 --- a/xlrd/examples/xlrdnameAPIdemo.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: cp1252 -*- - -## -# Module/script example of the xlrd API for extracting information -# about named references, named constants, etc. -# -#

Copyright © 2006 Stephen John Machin, Lingfo Pty Ltd

-#

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

-## - -import xlrd -import sys -import glob - -def scope_as_string(book, scope): - if 0 <= scope < book.nsheets: - return "sheet #%d (%r)" % (scope, book.sheet_names()[scope]) - if scope == -1: - return "Global" - if scope == -2: - return "Macro/VBA" - return "Unknown scope value (%r)" % scope - -def do_scope_query(book, scope_strg, show_contents=0, f=sys.stdout): - try: - qscope = int(scope_strg) - except ValueError: - if scope_strg == "*": - qscope = None # means "all' - else: - # so assume it's a sheet name ... - qscope = book.sheet_names().index(scope_strg) - print >> f, "%r => %d" % (scope_strg, qscope) - for nobj in book.name_obj_list: - if qscope is None or nobj.scope == qscope: - show_name_object(book, nobj, show_contents, f) - -def show_name_details(book, name, show_contents=0, f=sys.stdout): - """ - book -- Book object obtained from xlrd.open_workbook(). - name -- The name that's being investigated. - show_contents -- 0: Don't; 1: Non-empty cells only; 2: All cells - f -- Open output file handle. - """ - name_lcase = name.lower() # Excel names are case-insensitive. - nobj_list = book.name_map.get(name_lcase) - if not nobj_list: - print >> f, "%r: unknown name" % name - return - for nobj in nobj_list: - show_name_object(book, nobj, show_contents, f) - -def show_name_details_in_scope( - book, name, scope_strg, show_contents=0, f=sys.stdout, - ): - try: - scope = int(scope_strg) - except ValueError: - # so assume it's a sheet name ... - scope = book.sheet_names().index(scope_strg) - print >> f, "%r => %d" % (scope_strg, scope) - name_lcase = name.lower() # Excel names are case-insensitive. - while 1: - nobj = book.name_and_scope_map.get((name_lcase, scope)) - if nobj: - break - print >> f, "Name %r not found in scope %d" % (name, scope) - if scope == -1: - return - scope = -1 # Try again with global scope - print >> f, "Name %r found in scope %d" % (name, scope) - show_name_object(book, nobj, show_contents, f) - -def showable_cell_value(celltype, cellvalue, datemode): - if celltype == xlrd.XL_CELL_DATE: - try: - showval = xlrd.xldate_as_tuple(cellvalue, datemode) - except xlrd.XLDateError: - e1, e2 = sys.exc_info()[:2] - showval = "%s:%s" % (e1.__name__, e2) - elif celltype == xlrd.XL_CELL_ERROR: - showval = xlrd.error_text_from_code.get( - cellvalue, '' % cellvalue) - else: - showval = cellvalue - return showval - -def show_name_object(book, nobj, show_contents=0, f=sys.stdout): - print >> f, "\nName: %r, scope: %r (%s)" \ - % (nobj.name, nobj.scope, scope_as_string(book, nobj.scope)) - res = nobj.result - print >> f, "Formula eval result: %r" % res - if res is None: - return - # result should be an instance of the Operand class - kind = res.kind - value = res.value - if kind >= 0: - # A scalar, or unknown ... you've seen all there is to see. - pass - elif kind == xlrd.oREL: - # A list of Ref3D objects representing *relative* ranges - for i in xrange(len(value)): - ref3d = value[i] - print >> f, "Range %d: %r ==> %s"% (i, ref3d.coords, xlrd.rangename3drel(book, ref3d)) - elif kind == xlrd.oREF: - # A list of Ref3D objects - for i in xrange(len(value)): - ref3d = value[i] - print >> f, "Range %d: %r ==> %s"% (i, ref3d.coords, xlrd.rangename3d(book, ref3d)) - if not show_contents: - continue - datemode = book.datemode - for shx in xrange(ref3d.shtxlo, ref3d.shtxhi): - sh = book.sheet_by_index(shx) - print >> f, " Sheet #%d (%s)" % (shx, sh.name) - rowlim = min(ref3d.rowxhi, sh.nrows) - collim = min(ref3d.colxhi, sh.ncols) - for rowx in xrange(ref3d.rowxlo, rowlim): - for colx in xrange(ref3d.colxlo, collim): - cty = sh.cell_type(rowx, colx) - if cty == xlrd.XL_CELL_EMPTY and show_contents == 1: - continue - cval = sh.cell_value(rowx, colx) - sval = showable_cell_value(cty, cval, datemode) - print >> f, " (%3d,%3d) %-5s: %r" \ - % (rowx, colx, xlrd.cellname(rowx, colx), sval) - -if __name__ == "__main__": - def usage(): - text = """ -usage: xlrdnameAIPdemo.py glob_pattern name scope show_contents - -where: - "glob_pattern" designates a set of files - "name" is a name or '*' (all names) - "scope" is -1 (global) or a sheet number - or a sheet name or * (all scopes) - "show_contents" is one of 0 (no show), - 1 (only non-empty cells), or 2 (all cells) - -Examples (script name and glob_pattern arg omitted for brevity) - [Searching through book.name_obj_list] - * * 0 lists all names - * * 1 lists all names, showing referenced non-empty cells - * 1 0 lists all names local to the 2nd sheet - * Northern 0 lists all names local to the 'Northern' sheet - * -1 0 lists all names with global scope - [Initial direct access through book.name_map] - Sales * 0 lists all occurrences of "Sales" in any scope - [Direct access through book.name_and_scope_map] - Revenue -1 0 checks if "Revenue" exists in global scope - -""" - sys.stdout.write(text) - - if len(sys.argv) != 5: - usage() - sys.exit(0) - arg_pattern = sys.argv[1] # glob pattern e.g. "foo*.xls" - arg_name = sys.argv[2] # see below - arg_scope = sys.argv[3] # see below - arg_show_contents = int(sys.argv[4]) # 0: no show, 1: only non-empty cells, - # 2: all cells - for fname in glob.glob(arg_pattern): - book = xlrd.open_workbook(fname) - if arg_name == "*": - # Examine book.name_obj_list to find all names - # in a given scope ("*" => all scopes) - do_scope_query(book, arg_scope, arg_show_contents) - elif arg_scope == "*": - # Using book.name_map to find all usage of a name. - show_name_details(book, arg_name, arg_show_contents) - else: - # Using book.name_and_scope_map to find which if any instances - # of a name are visible in the given scope, which can be supplied - # as -1 (global) or a sheet number or a sheet name. - show_name_details_in_scope(book, arg_name, arg_scope, arg_show_contents) diff --git a/xlrd/formatting.py b/xlrd/formatting.py index f6a58b42..ca637b81 100644 --- a/xlrd/formatting.py +++ b/xlrd/formatting.py @@ -1,37 +1,35 @@ -# -*- coding: cp1252 -*- - -## -# Module for formatting information. -# -#

Copyright © 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. - -# 2010-10-30 SJM Added space after colon in "# coding" line to work around IBM iSeries Python bug -# 2009-05-31 SJM Fixed problem with non-zero reserved bits in some STYLE records in Mac Excel files -# 2008-08-03 SJM Ignore PALETTE record when Book.formatting_info is false -# 2008-08-03 SJM Tolerate up to 4 bytes trailing junk on PALETTE record -# 2008-05-10 SJM Do some XF checks only when Book.formatting_info is true -# 2008-02-08 SJM Preparation for Excel 2.0 support -# 2008-02-03 SJM Another tweak to is_date_format_string() -# 2007-12-04 SJM Added support for Excel 2.x (BIFF2) files. -# 2007-10-13 SJM Warning: style XF whose parent XF index != 0xFFF -# 2007-09-08 SJM Work around corrupt STYLE record -# 2007-07-11 SJM Allow for BIFF2/3-style FORMAT record in BIFF4/8 file +# -*- 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. +""" -DEBUG = 0 -import copy, re -from timemachine import * -from biffh import BaseObject, unpack_unicode, unpack_string, \ - upkbits, upkbitsL, fprintf, \ - FUN, FDT, FNU, FGE, FTX, XL_CELL_NUMBER, XL_CELL_DATE, \ - XL_FORMAT, XL_FORMAT2, \ - XLRDError +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), @@ -47,7 +45,7 @@ (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] @@ -68,7 +66,7 @@ (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, @@ -79,20 +77,18 @@ 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) -""" +# 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_", @@ -104,7 +100,7 @@ "Currency [0]", "Hyperlink", "Followed Hyperlink", - ] +] def initialise_colour_map(book): book.colour_map = {} @@ -123,18 +119,20 @@ def initialise_colour_map(book): # 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 # - for ci in ( - 0x51, # System ToolTip text colour (used in note objects) - 0x7FFF, # 32767, system window text colour for fonts - ): - book.colour_map[ci] = None + 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. + """ + 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(): @@ -149,14 +147,15 @@ def nearest_colour_index(colour_map, rgb, debug=0): 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) + 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 -## -# This mixin class exists solely so that Format, Font, and XF.... objects -# can be compared by value of their attributes. 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__ @@ -164,85 +163,96 @@ def __eq__(self, other): def __ne__(self, other): return self.__dict__ != other.__dict__ -## -# 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. -#
-- New in version 0.6.1 class Font(BaseObject, EqNeAttrs): - ## - # 1 = Characters are bold. Redundant; see "weight" attribute. + """ + 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 + + #: 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 the Formatting - # section at the start of this document. + + #: An explanation of "colour index" is given in :ref:`palette`. colour_index = 0 - ## - # 1 = Superscript, 2 = Subscript. + + #: 1 = Superscript, 2 = Subscript. escapement = 0 - ## - # 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) + + #: 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. + + #: 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 of the font (in twips). A twip = 1/20 of a point. height = 0 - ## - # 1 = Characters are italic. + + #: 1 = Characters are italic. italic = 0 - ## - # The name of the font. Example: u"Arial" - name = u"" - ## - # 1 = Characters are struck out. + + #: The name of the font. Example: ``"Arial"``. + name = UNICODE_LITERAL("") + + #: 1 = Characters are struck out. struck_out = 0 - ## - # 0 = None
- # 1 = Single; 0x21 (33) = Single accounting
- # 2 = Double; 0x22 (34) = Double accounting + + #: Values: + #: :: + #: + #: 0 = None + #: 1 = Single; 0x21 (33) = Single accounting + #: 2 = Double; 0x22 (34) = Double accounting underline_type = 0 - ## - # 1 = Characters are underlined. Redundant; see "underline_type" attribute. + + #: 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. + + #: 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) + + #: 1 = Font is outline style (Macintosh only) outline = 0 - ## - # 1 = Font is shadow style (Macintosh only) - shadow = 0 - # No methods ... + #: 1 = Font is shadow style (Macintosh only) + shadow = 0 def handle_efont(book, data): # BIFF2 only if not book.formatting_info: @@ -259,7 +269,7 @@ def handle_font(book, data): k = len(book.font_list) if k == 4: f = Font() - f.name = u'Dummy Font' + f.name = UNICODE_LITERAL('Dummy Font') f.font_index = k book.font_list.append(f) k += 1 @@ -269,7 +279,7 @@ def handle_font(book, data): if bv >= 50: ( f.height, option_flags, f.colour_index, f.weight, - f.escapement_type, f.underline_type, f.family, + f.escapement, f.underline_type, f.family, f.character_set, ) = unpack(' -- New in version 0.6.1 class Format(BaseObject, EqNeAttrs): - ## - # The key into Book.format_map + """ + "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 + + #: 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 = u'' + + #: The format string + format_str = UNICODE_LITERAL('') def __init__(self, format_key, ty, format_str): self.format_key = format_key @@ -388,7 +401,7 @@ def __init__(self, format_key, ty, format_str): 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 @@ -406,7 +419,7 @@ def __init__(self, format_key, ty, format_str): (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: @@ -414,37 +427,37 @@ def __init__(self, format_key, ty, format_str): std_format_code_types[x] = ty del lo, hi, ty, x -date_chars = u'ymdhs' # year, month/minute, day, hour, second +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 u'$-+/(): ': +for _c in UNICODE_LITERAL('$-+/(): '): skip_char_dict[_c] = 1 num_char_dict = { - u'0': 5, - u'#': 5, - u'?': 5, - } + UNICODE_LITERAL('0'): 5, + UNICODE_LITERAL('#'): 5, + UNICODE_LITERAL('?'): 5, +} non_date_formats = { - u'0.00E+00':1, - u'##0.0E+0':1, - u'General' :1, - u'GENERAL' :1, # OOo Calc 1.1.4 does this. - u'general' :1, # pyExcelerator 0.6.3 does this. - u'@' :1, - } + 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) -# u'"Yes";"Yes";"No"' -# u'"True";"True";"False"' -# u'"On";"On";"Off"' +# '"Yes";"Yes";"No"' +# '"True";"True";"False"' +# '"On";"On";"Off"' def is_date_format_string(book, fmt): # Heuristics: @@ -453,42 +466,42 @@ def is_date_format_string(book, fmt): # 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. u'General"."' hence get rid of "text" first. + # N.B. 'General"."' hence get rid of "text" first. # TODO: Find where formats are interpreted in Gnumeric - # TODO: u'[h]\\ \\h\\o\\u\\r\\s' ([h] means don't care about hours > 23) + # TODO: '[h]\\ \\h\\o\\u\\r\\s' ([h] means don't care about hours > 23) state = 0 s = '' - ignorable = skip_char_dict.has_key + for c in fmt: if state == 0: - if c == u'"': + if c == UNICODE_LITERAL('"'): state = 1 - elif c in ur"\_*": + elif c in UNICODE_LITERAL(r"\_*"): state = 2 - elif ignorable(c): + elif c in skip_char_dict: pass else: s += c elif state == 1: - if c == u'"': + 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 >> book.logfile, "is_date_format_string: reduced format is %r" % s + print("is_date_format_string: reduced format is %s" % REPR(s), file=book.logfile) s = fmt_bracketed_sub('', s) - if non_date_formats.has_key(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 date_char_dict.has_key(c): + if c in date_char_dict: date_count += date_char_dict[c] - elif num_char_dict.has_key(c): + elif c in num_char_dict: num_count += num_char_dict[c] elif c == separator: got_sep = 1 @@ -567,8 +580,7 @@ def handle_palette(book, data): blah = DEBUG or book.verbosity >= 2 n_colours, = unpack('= 50] - if ((DEBUG or book.verbosity >= 1) - and n_colours != expected_n_colours): + 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) @@ -596,7 +608,7 @@ def handle_palette(book, data): book.colour_map[8+i] = new_rgb if blah: if new_rgb != old_rgb: - print >> book.logfile, "%2d: %r -> %r" % (i, old_rgb, new_rgb) + print("%2d: %r -> %r" % (i, old_rgb, new_rgb), file=book.logfile) def palette_epilogue(book): # Check colour indexes in fonts etc. @@ -608,17 +620,15 @@ def palette_epilogue(book): cx = font.colour_index if cx == 0x7fff: # system window text colour continue - if book.colour_map.has_key(cx): + if cx in book.colour_map: book.colour_indexes_used[cx] = 1 elif book.verbosity: - print >> book.logfile, "Size of colour table:", len(book.colour_map) - print >> book.logfile, \ - "*** Font #%d (%r): colour index 0x%04x is unknown" \ - % (font.font_index, font.name, cx) + 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 = book.colour_indexes_used.keys() - used.sort() - print >> book.logfile, "\nColour indexes used:\n%r\n" % used + 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: @@ -627,8 +637,7 @@ def handle_style(book, data): bv = book.biff_version flag_and_xfx, built_in_id, level = unpack('> book.logfile, \ - "STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d" \ - % (built_in, xf_index, built_in_id, level) - print >> book.logfile, "raw bytes:", repr(data[2:]) + 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 >> book.logfile, \ - "WARNING *** A user-defined style has a zero-length 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: - print >> book.logfile, \ - "STYLE: built_in=%d xf_index=%d built_in_id=%d level=%d name=%r" \ - % (built_in, xf_index, built_in_id, level, name) + 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 = obj.__dict__.items() - alist.sort() + 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 book.colour_map.has_key(nobj): + if nobj in book.colour_map: book.colour_indexes_used[nobj] = 1 continue oname = obj.__class__.__name__ - print >> book.logfile, \ - "*** xf #%d : %s.%s = 0x%04x (unknown)" \ - % (orig_index, oname, attr, nobj) + 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 not book.format_map.has_key(x): + 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) @@ -694,7 +698,7 @@ def fill_in_standard_formats(book): book.format_map[x] = fmtobj def handle_xf(self, data): - ### self is a Book instance + # self is a Book instance # DEBUG = 0 blah = DEBUG or self.verbosity >= 3 bv = self.biff_version @@ -716,14 +720,15 @@ def handle_xf(self, data): fill_in_standard_formats(self) if bv >= 80: unpack_fmt = '> 2 - for attr_stem in \ - "format font alignment border background protection".split(): + 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 @@ -756,46 +768,54 @@ def handle_xf(self, data): (16, 0x007f0000, 'left_colour_index'), (23, 0x3f800000, 'right_colour_index'), (30, 0x40000000, 'diag_down'), - (31, 0x80000000L, 'diag_up'), - )) + (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, 0xFC000000L, 'fill_pattern'), - )) + (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 - for attr_stem in \ - "format font alignment border background protection".split(): + 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 @@ -803,11 +823,11 @@ def handle_xf(self, data): ( 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, 0xFE000000L, 'bottom_colour_index'), - )) + (25, 0xFE000000, 'bottom_colour_index'), + )) upkbits(xf.border, pkd_brdbkg2, ( ( 0, 0x00000007, 'top_line_style'), ( 3, 0x00000038, 'left_line_style'), @@ -815,32 +835,40 @@ def handle_xf(self, data): ( 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 - for attr_stem in \ - "format font alignment border background protection".split(): + 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 @@ -848,7 +876,7 @@ def handle_xf(self, data): ( 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'), @@ -857,32 +885,40 @@ def handle_xf(self, data): (16, 0x00070000, 'bottom_line_style'), (19, 0x00F80000, 'bottom_colour_index'), (24, 0x07000000, 'right_line_style'), - (27, 0xF8000000L, 'right_colour_index'), - )) + (27, 0xF8000000, 'right_colour_index'), + )) elif bv == 30: unpack_fmt = '> 2 - for attr_stem in \ - "format font alignment border background protection".split(): + 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 @@ -890,7 +926,7 @@ def handle_xf(self, data): ( 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'), @@ -899,24 +935,24 @@ def handle_xf(self, data): (16, 0x00070000, 'bottom_line_style'), (19, 0x00F80000, 'bottom_colour_index'), (24, 0x07000000, 'right_line_style'), - (27, 0xF8000000L, 'right_colour_index'), - )) + (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 :-( + ## 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(' xf.xf_index: fprintf(self.logfile, "NOTE !!! XF[%d]: parent_style_index is %d; out of order?\n", @@ -1057,214 +1099,226 @@ def initialise_book(book): handle_style, handle_xf, xf_epilogue, - ) + ) for method in methods: setattr(book.__class__, method.__name__, method) -## -#

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 the Formatting -# section at the start of this document. -# 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)".

-#
-- New in version 0.6.1 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:: - ## - # The colour index for the cell's top line + 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 + #: The colour index for the cell's bottom line bottom_colour_index = 0 - ## - # The colour index for the cell's left line + + #: The colour index for the cell's left line left_colour_index = 0 - ## - # The colour index for the cell's right line + + #: The colour index for the cell's right line right_colour_index = 0 - ## - # The colour index for the cell's diagonal lines, if any + + #: The colour index for the cell's diagonal lines, if any diag_colour_index = 0 - ## - # The line style for the cell's top line + + #: The line style for the cell's top line top_line_style = 0 - ## - # The line style for the cell's bottom line + + #: The line style for the cell's bottom line bottom_line_style = 0 - ## - # The line style for the cell's left line + + #: The line style for the cell's left line left_line_style = 0 - ## - # The line style for the cell's right line + + #: The line style for the cell's right line right_line_style = 0 - ## - # The line style for the cell's diagonal lines, if any + + #: 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 + + #: 1 = draw a diagonal from top left to bottom right diag_down = 0 - ## - # 1 = draw a diagonal from bottom left to top right + + #: 1 = draw a diagonal from bottom left to top right diag_up = 0 -## -# 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 explanation of "colour index" is given in the Formatting -# section at the start of this document. -#
-- New in version 0.6.1 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. - ## - # See section 3.11 of the OOo docs. + 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. + + #: See section 3.11 of the OOo docs. background_colour_index = 0 - ## - # See section 3.11 of the OOo docs. + + #: See section 3.11 of the OOo docs. pattern_colour_index = 0 -## -# 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. -#
-- New in version 0.6.1 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 + #: Values: section 6.115 (p 214) of OOo docs hor_align = 0 - ## - # Values: section 6.115 (p 215) of OOo docs + + #: 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 - # "orientation" attribute; this will be mapped (without loss) - # into "rotation". + + #: 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 + + #: 1 = text is wrapped at right margin text_wrapped = 0 - ## - # A number in range(15). + + #: A number in ``range(15)``. indent_level = 0 - ## - # 1 = shrink font size to fit text into cell. + + #: 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 -## -# 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. -#
-- New in version 0.6.1 + #: 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. - ## - # 1 = Cell is prevented from being changed, moved, resized, or deleted - # (only if the sheet is protected). + .. 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 -## -# eXtended Formatting information for cells, rows, columns and styles. -#
-- New in version 0.6.1 -# -#

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 that the API -# provides both "raw" XFs and "computed" XFs -- in the latter case, cell XFs -# have had the above inheritance mechanism applied. -#

+ #: 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. - ## - # 0 = cell XF, 1 = style XF + 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 + + #: 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 Book.xf_list + + #: Index into :attr:`~xlrd.book.Book.xf_list` xf_index = 0 - ## - # Index into Book.font_list + + #: Index into :attr:`~xlrd.book.Book.font_list` font_index = 0 - ## - # Key into 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. - #

+ + #: 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 XFProtection object. + + #: An instance of an :class:`XFProtection` object. protection = None - ## - # An instance of an XFBackground object. + + #: An instance of an :class:`XFBackground` object. background = None - ## - # An instance of an XFAlignment object. + + #: An instance of an :class:`XFAlignment` object. alignment = None - ## - # An instance of an XFBorder object. + + #: An instance of an :class:`XFBorder` object. border = None diff --git a/xlrd/formula.py b/xlrd/formula.py index 37477348..00e4464a 100644 --- a/xlrd/formula.py +++ b/xlrd/formula.py @@ -1,21 +1,24 @@ -# -*- coding: cp1252 -*- +# -*- 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 -## -# Module for parsing/evaluating Microsoft Excel formulas. -# -#

Copyright © 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. - -from __future__ import nested_scopes import copy +import operator as opr from struct import unpack -from timemachine import * -from biffh import unpack_unicode_update_pos, unpack_string_update_pos, \ - XLRDError, hex_char_dump, error_text_from_code, BaseObject + +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', @@ -30,7 +33,8 @@ 'FMLA_TYPE_COND_FMT', 'FMLA_TYPE_DATA_VAL', 'FMLA_TYPE_NAME', - ] + 'Operand', 'Ref3D', +] FMLA_TYPE_CELL = 1 FMLA_TYPE_SHARED = 2 @@ -48,7 +52,7 @@ 8 : 'COND-FMT', 16: 'DATA-VAL', 32: 'NAME', - } +} _TOKEN_NOT_ALLOWED = { 0x01: ALL_FMLA_TYPES - FMLA_TYPE_CELL, # tExp @@ -64,7 +68,7 @@ 0x2C: FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tRefN 0x2D: FMLA_TYPE_CELL + FMLA_TYPE_ARRAY, # tAreaN # plus weird stuff like tMem* - }.get +}.get oBOOL = 3 oERR = 4 @@ -84,7 +88,7 @@ 3 : "oBOOL", 4 : "oERR", 5 : "oMSNG", - } +} listsep = ',' #### probably should depend on locale @@ -108,7 +112,7 @@ 50 : sztab3, 70 : sztab3, 80 : sztab4, - } +} # For debugging purposes ... the name for each opcode # (without the prefix "t" used on OOo docs) @@ -376,7 +380,7 @@ 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 @@ -388,21 +392,18 @@ 0x20: "Assign", 0x40: "Space", 0x41: "SpaceVolatile", - } +} -_error_opcodes = {} -for _x in [0x07, 0x08, 0x0A, 0x0B, 0x1C, 0x1D, 0x2F]: - _error_opcodes[_x] = 1 -is_error_opcode = _error_opcodes.has_key +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([ + 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 @@ -472,32 +473,32 @@ def get_externsheet_local_range(bk, refx, blah=0): try: info = bk._externsheet_info[refx] except IndexError: - print >> bk.logfile, "!!! get_externsheet_local_range: refx=%d, not in range(%d)" \ - % (refx, len(bk._externsheet_info)) + print("!!! get_externsheet_local_range: refx=%d, not in range(%d)" + % (refx, len(bk._externsheet_info)), file=bk.logfile) return (-101, -101) ref_recordx, ref_first_sheetx, ref_last_sheetx = info if ref_recordx == bk._supbook_addins_inx: if blah: - print >> bk.logfile, "/// get_externsheet_local_range(refx=%d) -> addins %r" % (refx, info) + print("/// get_externsheet_local_range(refx=%d) -> 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 >> bk.logfile, "/// get_externsheet_local_range(refx=%d) -> external %r" % (refx, info) + 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 >> bk.logfile, "/// get_externsheet_local_range(refx=%d) -> unspecified sheet %r" % (refx, info) + 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 >> bk.logfile, "/// get_externsheet_local_range(refx=%d) -> deleted sheet(s)" % (refx, ) + 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 >> bk.logfile, "/// get_externsheet_local_range(refx=%d) -> %r" % (refx, info) - print >> bk.logfile, "--- first/last sheet not in range(%d)" % nsheets + 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] @@ -509,16 +510,16 @@ def get_externsheet_local_range_b57( bk, raw_extshtx, ref_first_sheetx, ref_last_sheetx, blah=0): if raw_extshtx > 0: if blah: - print >> bk.logfile, "/// get_externsheet_local_range_b57(raw_extshtx=%d) -> external" % raw_extshtx + 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 >> bk.logfile, "/// get_externsheet_local_range_b57(%d, %d, %d) -> ???" \ - % (raw_extshtx, ref_first_sheetx, ref_last_sheetx) - print >> bk.logfile, "--- first/last sheet not in range(%d)" % nsheets + 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] @@ -530,80 +531,80 @@ class FormulaError(Exception): pass -## -# Used in evaluating formulas. -# The following table describes the kinds and how their values -# are represented.

-# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -#
Kind symbolKind numberValue representation
oBOOL3integer: 0 => False; 1 => True
oERR4None, or an int error code (same as XL_CELL_ERROR in the Cell class). -#
oMSNG5Used by Excel as a placeholder for a missing (not supplied) function -# argument. Should *not* appear as a final formula result. Value is None.
oNUM2A float. Note that there is no way of distinguishing dates.
oREF-1The value is either None or a non-empty list of -# absolute Ref3D instances.
-#
oREL-2The value is None or a non-empty list of -# fully or partially relative Ref3D instances. -#
oSTRG1A Unicode string.
oUNK0The kind is unknown or ambiguous. The value is None
-#

- class Operand(object): + """ + Used in evaluating formulas. + The following table describes the kinds and how their values + are represented. + + .. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Kind symbolKind numberValue representation
oBOOL3integer: 0 => False; 1 => True
oERR4None, or an int error code (same as XL_CELL_ERROR in the Cell class). +
oMSNG5Used by Excel as a placeholder for a missing (not supplied) function + argument. Should *not* appear as a final formula result. Value is None.
oNUM2A float. Note that there is no way of distinguishing dates.
oREF-1The value is either None or a non-empty list of + absolute Ref3D instances.
+
oREL-2The value is None or a non-empty list of + fully or partially relative Ref3D instances. +
oSTRG1A Unicode string.
oUNK0The 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. + #: 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. + + #: 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. + + #: 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='?'): @@ -621,45 +622,55 @@ def __repr__(self): return "Operand(kind=%s, value=%r, text=%r)" \ % (kind_text, self.value, self.text) -if CAN_SUBCLASS_BUILTIN: - _ref3d_base = tuple -else: - _ref3d_base = object - -## -#

Represents an absolute or relative 3-dimensional reference to a box -# of one or more cells.
-# -- New in version 0.6.0 -#

-# -#

The coords attribute is a tuple of the form:
-# (shtxlo, shtxhi, rowxlo, rowxhi, colxlo, colxhi)
-# where 0 <= thingxlo <= thingx < thingxhi.
-# Note that 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: 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 that 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 also that 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 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, ...). - -class Ref3D(_ref3d_base): + +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] @@ -685,7 +696,6 @@ def __repr__(self): tConcat = 0x08 tLT, tLE, tEQ, tGE, tGT, tNE = range(0x09, 0x0F) -import operator as opr def nop(x): return x @@ -700,8 +710,8 @@ 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. + """ + Attempt to emulate Excel's default conversion from number to string. """ s = str(num) if s.endswith(".0"): @@ -716,7 +726,7 @@ def num2strg(num): 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.div, 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, '<'), @@ -725,13 +735,13 @@ def num2strg(num): 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 @@ -747,8 +757,8 @@ def evaluate_name_formula(bk, nobj, namex, blah=0, level=0): bv = bk.biff_version reldelta = 1 # All defined name formulas use "Method B" [OOo docs] if blah: - print >> bk.logfile, "::: evaluate_name_formula %r %r %d %d %r level=%d" \ - % (namex, nobj.name, fmlalen, bv, data, level) + 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") @@ -775,7 +785,7 @@ def do_binop(opcd, stk): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) resop = Operand(result_kind, None, rank, otext) try: bconv = argdict[bop.kind] @@ -790,7 +800,7 @@ def do_binop(opcd, stk): aval = aconv(aop.value) result = func(aval, bval) if result_kind == oBOOL: - result = intbool(result) # -> 1 or 0 + result = 1 if result else 0 resop.value = result stk.append(resop) @@ -805,7 +815,7 @@ def do_unaryop(opcode, result_kind, stk): aop.text, ')'[:aop.rank < rank], sym2, - ]) + ]) if val is not None: val = func(val) stk.append(Operand(result_kind, val, rank, otext)) @@ -819,7 +829,7 @@ def not_in_name_formula(op_arg, oname_arg): stack = [unk_opnd] while 0 <= pos < fmlalen: - op = ord(data[pos]) + op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: @@ -829,9 +839,9 @@ def not_in_name_formula(op_arg, oname_arg): oname = onames[opx] # + [" RVA"][optype] sz = sztab[opx] if blah: - print >> bk.logfile, "Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh" \ - % (pos, op, oname, sz, opcode, optype) - print >> bk.logfile, "Stack =", stack + 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) @@ -845,7 +855,7 @@ def not_in_name_formula(op_arg, oname_arg): # tLT, ..., tNE do_binop(opcode, stack) elif opcode == 0x0F: # tIsect - if blah: print >> bk.logfile, "tIsect pre", stack + if blah: print("tIsect pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -859,7 +869,7 @@ def not_in_name_formula(op_arg, oname_arg): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) res = Operand(oREF) res.text = otext if bop.kind == oERR or aop.kind == oERR: @@ -893,9 +903,9 @@ def not_in_name_formula(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tIsect post", stack + if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x10: # tList - if blah: print >> bk.logfile, "tList pre", stack + if blah: print("tList pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -909,7 +919,7 @@ def not_in_name_formula(op_arg, oname_arg): '('[: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 @@ -924,9 +934,9 @@ def not_in_name_formula(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tList post", stack + if blah: print("tList post", stack, file=bk.logfile) elif opcode == 0x11: # tRange - if blah: print >> bk.logfile, "tRange pre", stack + if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -940,10 +950,10 @@ def not_in_name_formula(op_arg, oname_arg): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: - res = oERR + res.kind = oERR elif bop.kind == oREF == aop.kind: if aop.value is not None and bop.value is not None: assert len(aop.value) == 1 @@ -965,7 +975,7 @@ def not_in_name_formula(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tRange post", stack + 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 @@ -981,7 +991,7 @@ def not_in_name_formula(op_arg, oname_arg): strg, newpos = unpack_unicode_update_pos( data, pos+1, lenlen=1) sz = newpos - pos - if blah: print >> bk.logfile, " sz=%d strg=%r" % (sz, strg) + 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 @@ -996,7 +1006,7 @@ def not_in_name_formula(op_arg, oname_arg): sz = nc * 2 + 6 elif subop == 0x10: # Sum (single arg) sz = 4 - if blah: print >> bk.logfile, "tAttrSum", stack + if blah: print("tAttrSum", stack, file=bk.logfile) assert len(stack) >= 1 aop = stack[-1] otext = 'SUM(%s)' % aop.text @@ -1004,8 +1014,8 @@ def not_in_name_formula(op_arg, oname_arg): else: sz = 4 if blah: - print >> bk.logfile, " subop=%02xh subname=t%s sz=%d nc=%02xh" \ - % (subop, subname, sz, nc) + 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") @@ -1037,17 +1047,17 @@ def not_in_name_formula(op_arg, oname_arg): funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0] func_attrs = func_defs.get(funcx, None) if not func_attrs: - print >> bk.logfile, "*** formula/tFunc unknown FuncID:%d" \ - % funcx + print("*** formula/tFunc unknown FuncID:%d" + % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, nargs = func_attrs[:2] if blah: - print >> bk.logfile, " FuncID=%d name=%s nargs=%d" \ - % (funcx, func_name, nargs) + 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:]]) + argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) del stack[-nargs:] else: @@ -1060,32 +1070,32 @@ def not_in_name_formula(op_arg, oname_arg): prompt, nargs = divmod(nargs, 128) macro, funcx = divmod(funcx, 32768) if blah: - print >> bk.logfile, " FuncID=%d nargs=%d macro=%d prompt=%d" \ - % (funcx, nargs, macro, prompt) + print(" FuncID=%d nargs=%d macro=%d prompt=%d" + % (funcx, nargs, macro, prompt), file=bk.logfile) func_attrs = func_defs.get(funcx, None) if not func_attrs: - print >> bk.logfile, "*** formula/tFuncVar unknown FuncID:%d" \ - % funcx + print("*** formula/tFuncVar unknown FuncID:%d" + % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, minargs, maxargs = func_attrs[:3] if blah: - print >> bk.logfile, " name: %r, min~max args: %d~%d" \ - % (func_name, minargs, maxargs) + print(" name: %r, min~max args: %d~%d" + % (func_name, minargs, maxargs), file=bk.logfile) assert minargs <= nargs <= maxargs assert len(stack) >= nargs assert len(stack) >= nargs - argtext = listsep.join([arg.text for arg in 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 >> bk.logfile, "IF testarg kind?" + print("IF testarg kind?", file=bk.logfile) elif testarg.value not in (0, 1): if blah and testarg.value is not None: - print >> bk.logfile, "IF testarg value?" + print("IF testarg value?", file=bk.logfile) else: if nargs == 2 and not testarg.value: # IF(FALSE, tv) => FALSE @@ -1098,7 +1108,7 @@ def not_in_name_formula(op_arg, oname_arg): else: res.kind, res.value = chosen.kind, chosen.value if blah: - print >> bk.logfile, "$$$$$$ IF => constant" + print("$$$$$$ IF => constant", file=bk.logfile) elif funcx == 100: # CHOOSE testarg = stack[-nargs] if testarg.kind == oNUM: @@ -1113,19 +1123,18 @@ def not_in_name_formula(op_arg, oname_arg): elif opcode == 0x03: #tName tgtnamex = unpack("> bk.logfile, " tgtnamex=%d" % tgtnamex + if blah: print(" tgtnamex=%d" % tgtnamex, file=bk.logfile) 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 tgtobj.macro or tgtobj.binary or tgtobj.any_err: if blah: tgtobj.dump( bk.logfile, header="!!! tgtobj has problems!!!", 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 @@ -1139,17 +1148,17 @@ def not_in_name_formula(op_arg, oname_arg): res.text = "%s!%s" \ % (bk._sheet_names[tgtobj.scope], tgtobj.name) if blah: - print >> bk.logfile, " tName: setting text to", repr(res.text) + print(" tName: setting text to", repr(res.text), file=bk.logfile) spush(res) elif opcode == 0x04: # tRef # not_in_name_formula(op, oname) res = get_cell_addr(data, pos+1, bv, reldelta) - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) rowx, colx, row_rel, col_rel = res shx1 = shx2 = 0 ####### N.B. relative to the CURRENT SHEET any_rel = 1 coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if optype == 1: relflags = (1, 1, row_rel, row_rel, col_rel, col_rel) @@ -1158,13 +1167,13 @@ def not_in_name_formula(op_arg, oname_arg): elif opcode == 0x05: # tArea # not_in_name_formula(op, oname) res1, res2 = get_cell_range_addr(data, pos+1, bv, reldelta) - if blah: print >> bk.logfile, " ", res1, res2 + if blah: print(" ", res1, res2, file=bk.logfile) rowx1, colx1, row_rel1, col_rel1 = res1 rowx2, colx2, row_rel2, col_rel2 = res2 shx1 = shx2 = 0 ####### N.B. relative to the CURRENT SHEET any_rel = 1 coords = (shx1, shx2+1, rowx1, rowx2+1, colx1, colx2+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if optype == 1: relflags = (1, 1, row_rel1, row_rel2, col_rel1, col_rel2) @@ -1174,7 +1183,7 @@ def not_in_name_formula(op_arg, oname_arg): not_in_name_formula(op, oname) elif opcode == 0x09: # tMemFunc nb = unpack("> bk.logfile, " %d bytes of cell ref formula" % nb + if blah: print(" %d bytes of cell ref formula" % nb, file=bk.logfile) # no effect on stack elif opcode == 0x0C: #tRefN not_in_name_formula(op, oname) @@ -1196,10 +1205,9 @@ def not_in_name_formula(op_arg, oname_arg): shx1, shx2 = get_externsheet_local_range(bk, refx, blah) else: res = get_cell_addr(data, pos+15, bv, reldelta) - raw_extshtx, raw_shx1, raw_shx2 = \ - unpack("> bk.logfile, "tRef3d", raw_extshtx, raw_shx1, raw_shx2 + print("tRef3d", raw_extshtx, raw_shx1, raw_shx2, file=bk.logfile) shx1, shx2 = get_externsheet_local_range_b57( bk, raw_extshtx, raw_shx1, raw_shx2, blah) rowx, colx, row_rel, col_rel = res @@ -1207,7 +1215,7 @@ def not_in_name_formula(op_arg, oname_arg): any_rel = any_rel or is_rel coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1) any_err |= shx1 < -1 - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if is_rel: relflags = (0, 0, row_rel, row_rel, col_rel, col_rel) @@ -1229,10 +1237,9 @@ def not_in_name_formula(op_arg, oname_arg): shx1, shx2 = get_externsheet_local_range(bk, refx, blah) else: res1, res2 = get_cell_range_addr(data, pos+15, bv, reldelta) - raw_extshtx, raw_shx1, raw_shx2 = \ - unpack("> bk.logfile, "tArea3d", raw_extshtx, raw_shx1, raw_shx2 + print("tArea3d", raw_extshtx, raw_shx1, raw_shx2, file=bk.logfile) shx1, shx2 = get_externsheet_local_range_b57( bk, raw_extshtx, raw_shx1, raw_shx2, blah) any_err |= shx1 < -1 @@ -1241,7 +1248,7 @@ def not_in_name_formula(op_arg, oname_arg): is_rel = row_rel1 or col_rel1 or row_rel2 or col_rel2 any_rel = any_rel or is_rel coords = (shx1, shx2+1, rowx1, rowx2+1, colx1, colx2+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if is_rel: relflags = (0, 0, row_rel1, row_rel2, col_rel1, col_rel2) @@ -1275,11 +1282,10 @@ def not_in_name_formula(op_arg, oname_arg): else: dodgy = 1 if blah: - print >> bk.logfile, \ - " origrefx=%d refx=%d tgtnamex=%d dodgy=%d" \ - % (origrefx, refx, tgtnamex, dodgy) + 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 !!!!" + if blah: print("!!!! Self-referential !!!!", file=bk.logfile) dodgy = any_err = 1 if not dodgy: if bv >= 80: @@ -1301,14 +1307,13 @@ def not_in_name_formula(op_arg, oname_arg): 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 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 @@ -1322,25 +1327,25 @@ def not_in_name_formula(op_arg, oname_arg): res.text = "%s!%s" \ % (bk._sheet_names[tgtobj.scope], tgtobj.name) if blah: - print >> bk.logfile, " tNameX: setting text to", repr(res.text) + print(" tNameX: setting text to", repr(res.text), file=bk.logfile) spush(res) - elif is_error_opcode(opcode): + elif opcode in error_opcodes: any_err = 1 spush(error_opnd) else: if blah: - print >> bk.logfile, "FORMULA: /// Not handled yet: t" + oname + 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 >> bk.logfile, "End of formula. level=%d any_rel=%d any_err=%d stack=%r" % \ - (level, not not any_rel, any_err, stack) + 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 >> bk.logfile, "*** Stack has unprocessed args" - print >> bk.logfile + print("*** Stack has unprocessed args", file=bk.logfile) + print(file=bk.logfile) nobj.stack = stack if len(stack) != 1: nobj.result = None @@ -1353,16 +1358,16 @@ def not_in_name_formula(op_arg, oname_arg): #### under construction ############################################################################# def decompile_formula(bk, fmla, fmlalen, - fmlatype=None, browx=None, bcolx=None, - blah=0, level=0, r1c1=0): + 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 >> bk.logfile, "::: decompile_formula len=%d fmlatype=%r browx=%r bcolx=%r reldelta=%d %r level=%d" \ - % (fmlalen, fmlatype, browx, bcolx, reldelta, data, level) + 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") @@ -1371,7 +1376,6 @@ def decompile_formula(bk, fmla, fmlalen, stack = [] any_rel = 0 any_err = 0 - any_external = 0 unk_opnd = Operand(oUNK, None) error_opnd = Operand(oERR, None) spush = stack.append @@ -1389,7 +1393,7 @@ def do_binop(opcd, stk): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) resop = Operand(result_kind, None, rank, otext) stk.append(resop) @@ -1403,20 +1407,20 @@ def do_unaryop(opcode, result_kind, stk): 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 >> bk.logfile, msg + print(msg, file=bk.logfile) # raise FormulaError(msg) if fmlalen == 0: stack = [unk_opnd] while 0 <= pos < fmlalen: - op = ord(data[pos]) + op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: @@ -1426,9 +1430,9 @@ def unexpected_opcode(op_arg, oname_arg): oname = onames[opx] # + [" RVA"][optype] sz = sztab[opx] if blah: - print >> bk.logfile, "Pos:%d Op:0x%02x opname:t%s Sz:%d opcode:%02xh optype:%02xh" \ - % (pos, op, oname, sz, opcode, optype) - print >> bk.logfile, "Stack =", stack + 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) @@ -1453,7 +1457,7 @@ def unexpected_opcode(op_arg, oname_arg): # tLT, ..., tNE do_binop(opcode, stack) elif opcode == 0x0F: # tIsect - if blah: print >> bk.logfile, "tIsect pre", stack + if blah: print("tIsect pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -1467,7 +1471,7 @@ def unexpected_opcode(op_arg, oname_arg): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) res = Operand(oREF) res.text = otext if bop.kind == oERR or aop.kind == oERR: @@ -1487,9 +1491,9 @@ def unexpected_opcode(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tIsect post", stack + if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x10: # tList - if blah: print >> bk.logfile, "tList pre", stack + if blah: print("tList pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -1503,7 +1507,7 @@ def unexpected_opcode(op_arg, oname_arg): '('[: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 @@ -1514,9 +1518,9 @@ def unexpected_opcode(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tList post", stack + if blah: print("tList post", stack, file=bk.logfile) elif opcode == 0x11: # tRange - if blah: print >> bk.logfile, "tRange pre", stack + if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -1530,7 +1534,7 @@ def unexpected_opcode(op_arg, oname_arg): '('[:bop.rank < rank], bop.text, ')'[:bop.rank < rank], - ]) + ]) res = Operand(oREF, None, rank, otext) if bop.kind == oERR or aop.kind == oERR: res = oERR @@ -1539,7 +1543,7 @@ def unexpected_opcode(op_arg, oname_arg): else: pass spush(res) - if blah: print >> bk.logfile, "tRange post", stack + 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 @@ -1555,7 +1559,7 @@ def unexpected_opcode(op_arg, oname_arg): strg, newpos = unpack_unicode_update_pos( data, pos+1, lenlen=1) sz = newpos - pos - if blah: print >> bk.logfile, " sz=%d strg=%r" % (sz, strg) + 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 @@ -1570,7 +1574,7 @@ def unexpected_opcode(op_arg, oname_arg): sz = nc * 2 + 6 elif subop == 0x10: # Sum (single arg) sz = 4 - if blah: print >> bk.logfile, "tAttrSum", stack + if blah: print("tAttrSum", stack, file=bk.logfile) assert len(stack) >= 1 aop = stack[-1] otext = 'SUM(%s)' % aop.text @@ -1578,8 +1582,8 @@ def unexpected_opcode(op_arg, oname_arg): else: sz = 4 if blah: - print >> bk.logfile, " subop=%02xh subname=t%s sz=%d nc=%02xh" \ - % (subop, subname, sz, nc) + 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") @@ -1611,16 +1615,16 @@ def unexpected_opcode(op_arg, oname_arg): funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb])[0] func_attrs = func_defs.get(funcx, None) if not func_attrs: - print >> bk.logfile, "*** formula/tFunc unknown FuncID:%d" % funcx + print("*** formula/tFunc unknown FuncID:%d" % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, nargs = func_attrs[:2] if blah: - print >> bk.logfile, " FuncID=%d name=%s nargs=%d" \ - % (funcx, func_name, nargs) + 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:]]) + argtext = listsep.join(arg.text for arg in stack[-nargs:]) otext = "%s(%s)" % (func_name, argtext) del stack[-nargs:] else: @@ -1633,26 +1637,26 @@ def unexpected_opcode(op_arg, oname_arg): prompt, nargs = divmod(nargs, 128) macro, funcx = divmod(funcx, 32768) if blah: - print >> bk.logfile, " FuncID=%d nargs=%d macro=%d prompt=%d" \ - % (funcx, nargs, macro, prompt) + print(" FuncID=%d nargs=%d macro=%d prompt=%d" + % (funcx, nargs, macro, prompt), file=bk.logfile) #### TODO #### if funcx == 255: # call add-in function if funcx == 255: func_attrs = ("CALL_ADDIN", 1, 30) else: func_attrs = func_defs.get(funcx, None) if not func_attrs: - print >> bk.logfile, "*** formula/tFuncVar unknown FuncID:%d" \ - % funcx + print("*** formula/tFuncVar unknown FuncID:%d" + % funcx, file=bk.logfile) spush(unk_opnd) else: func_name, minargs, maxargs = func_attrs[:3] if blah: - print >> bk.logfile, " name: %r, min~max args: %d~%d" \ - % (func_name, minargs, maxargs) + print(" name: %r, min~max args: %d~%d" + % (func_name, minargs, maxargs), file=bk.logfile) assert minargs <= nargs <= maxargs assert len(stack) >= nargs assert len(stack) >= nargs - argtext = listsep.join([arg.text for arg in 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:] @@ -1660,19 +1664,19 @@ def unexpected_opcode(op_arg, oname_arg): elif opcode == 0x03: #tName tgtnamex = unpack("> bk.logfile, " tgtnamex=%d" % tgtnamex + if blah: print(" tgtnamex=%d" % tgtnamex, file=bk.logfile) 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 >> bk.logfile, " tName: setting text to", repr(otext) + print(" tName: setting text to", repr(otext), file=bk.logfile) res = Operand(oUNK, None, LEAF_RANK, otext) spush(res) elif opcode == 0x04: # tRef res = get_cell_addr(data, pos+1, bv, reldelta, browx, bcolx) - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) rowx, colx, row_rel, col_rel = res is_rel = row_rel or col_rel if is_rel: @@ -1685,17 +1689,16 @@ def unexpected_opcode(op_arg, oname_arg): elif opcode == 0x05: # tArea res1, res2 = get_cell_range_addr( data, pos+1, bv, reldelta, browx, bcolx) - if blah: print >> bk.logfile, " ", res1, res2 + 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) - is_rel = intbool(sum(relflags)) - if is_rel: + if sum(relflags): # relative okind = oREL else: okind = oREF - if blah: print >> bk.logfile, " ", coords, relflags + if blah: print(" ", coords, relflags, file=bk.logfile) otext = rangename2drel(coords, relflags, browx, bcolx, r1c1) res = Operand(okind, None, LEAF_RANK, otext) spush(res) @@ -1703,13 +1706,13 @@ def unexpected_opcode(op_arg, oname_arg): not_in_name_formula(op, oname) elif opcode == 0x09: # tMemFunc nb = unpack("> bk.logfile, " %d bytes of cell ref formula" % nb + if blah: print(" %d bytes of cell ref formula" % nb, file=bk.logfile) # no effect on stack elif opcode == 0x0C: #tRefN res = get_cell_addr(data, pos+1, bv, reldelta, browx, bcolx) # note *ALL* tRefN usage has signed offset for relative addresses any_rel = 1 - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) rowx, colx, row_rel, col_rel = res is_rel = row_rel or col_rel if is_rel: @@ -1726,17 +1729,16 @@ def unexpected_opcode(op_arg, oname_arg): # if blah: print >> bk.logfile, " ", res res1, res2 = get_cell_range_addr( data, pos+1, bv, reldelta, browx, bcolx) - if blah: print >> bk.logfile, " ", res1, res2 + 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) - is_rel = intbool(sum(relflags)) - if is_rel: + if sum(relflags): # relative okind = oREL else: okind = oREF - if blah: print >> bk.logfile, " ", coords, relflags + if blah: print(" ", coords, relflags, file=bk.logfile) otext = rangename2drel(coords, relflags, browx, bcolx, r1c1) res = Operand(okind, None, LEAF_RANK, otext) spush(res) @@ -1747,10 +1749,9 @@ def unexpected_opcode(op_arg, oname_arg): shx1, shx2 = get_externsheet_local_range(bk, refx, blah) else: res = get_cell_addr(data, pos+15, bv, reldelta, browx, bcolx) - raw_extshtx, raw_shx1, raw_shx2 = \ - unpack("> bk.logfile, "tRef3d", raw_extshtx, raw_shx1, raw_shx2 + print("tRef3d", raw_extshtx, raw_shx1, raw_shx2, file=bk.logfile) shx1, shx2 = get_externsheet_local_range_b57( bk, raw_extshtx, raw_shx1, raw_shx2, blah) rowx, colx, row_rel, col_rel = res @@ -1758,7 +1759,7 @@ def unexpected_opcode(op_arg, oname_arg): any_rel = any_rel or is_rel coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1) any_err |= shx1 < -1 - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if is_rel: relflags = (0, 0, row_rel, row_rel, col_rel, col_rel) @@ -1779,10 +1780,9 @@ def unexpected_opcode(op_arg, oname_arg): shx1, shx2 = get_externsheet_local_range(bk, refx, blah) else: res1, res2 = get_cell_range_addr(data, pos+15, bv, reldelta) - raw_extshtx, raw_shx1, raw_shx2 = \ - unpack("> bk.logfile, "tArea3d", raw_extshtx, raw_shx1, raw_shx2 + print("tArea3d", raw_extshtx, raw_shx1, raw_shx2, file=bk.logfile) shx1, shx2 = get_externsheet_local_range_b57( bk, raw_extshtx, raw_shx1, raw_shx2, blah) any_err |= shx1 < -1 @@ -1791,7 +1791,7 @@ def unexpected_opcode(op_arg, oname_arg): is_rel = row_rel1 or col_rel1 or row_rel2 or col_rel2 any_rel = any_rel or is_rel coords = (shx1, shx2+1, rowx1, rowx2+1, colx1, colx2+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) res = Operand(oUNK, None) if is_rel: relflags = (0, 0, row_rel1, row_rel2, col_rel1, col_rel2) @@ -1822,9 +1822,8 @@ def unexpected_opcode(op_arg, oname_arg): else: dodgy = 1 if blah: - print >> bk.logfile, \ - " origrefx=%d refx=%d tgtnamex=%d dodgy=%d" \ - % (origrefx, refx, tgtnamex, dodgy) + 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 @@ -1856,26 +1855,26 @@ def unexpected_opcode(op_arg, oname_arg): otext = "%s!%s" \ % (bk._sheet_names[tgtobj.scope], tgtobj.name) if blah: - print >> bk.logfile, " tNameX: setting text to", repr(res.text) + print(" tNameX: setting text to", repr(res.text), file=bk.logfile) res = Operand(okind, ovalue, LEAF_RANK, otext) spush(res) - elif is_error_opcode(opcode): + elif opcode in error_opcodes: any_err = 1 spush(error_opnd) else: if blah: - print >> bk.logfile, "FORMULA: /// Not handled yet: t" + oname + 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 >> bk.logfile, "End of formula. level=%d any_rel=%d any_err=%d stack=%r" % \ - (level, not not any_rel, any_err, stack) + 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 >> bk.logfile, "*** Stack has unprocessed args" - print >> bk.logfile + print("*** Stack has unprocessed args", file=bk.logfile) + print(file=bk.logfile) if len(stack) != 1: result = None @@ -1886,7 +1885,7 @@ def unexpected_opcode(op_arg, oname_arg): #### under deconstruction ### def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): if blah: - print >> bk.logfile, "dump_formula", fmlalen, bv, len(data) + 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] @@ -1896,7 +1895,7 @@ def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): any_err = 0 spush = stack.append while 0 <= pos < fmlalen: - op = ord(data[pos]) + op = BYTES_ORD(data[pos]) opcode = op & 0x1f optype = (op & 0x60) >> 5 if optype: @@ -1907,22 +1906,22 @@ def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): sz = sztab[opx] if blah: - print >> bk.logfile, "Pos:%d Op:0x%02x Name:t%s Sz:%d opcode:%02xh optype:%02xh" \ - % (pos, op, oname, sz, opcode, optype) + 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("> bk.logfile, " ", (rowx, colx) + if blah: print(" ", (rowx, colx), file=bk.logfile) elif opcode == 0x10: # tList - if blah: print >> bk.logfile, "tList pre", stack + if blah: print("tList pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() spush(aop + bop) - if blah: print >> bk.logfile, "tlist post", stack + if blah: print("tlist post", stack, file=bk.logfile) elif opcode == 0x11: # tRange - if blah: print >> bk.logfile, "tRange pre", stack + if blah: print("tRange pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -1930,9 +1929,9 @@ def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): assert len(bop) == 1 result = do_box_funcs(tRangeFuncs, aop[0], bop[0]) spush(result) - if blah: print >> bk.logfile, "tRange post", stack + if blah: print("tRange post", stack, file=bk.logfile) elif opcode == 0x0F: # tIsect - if blah: print >> bk.logfile, "tIsect pre", stack + if blah: print("tIsect pre", stack, file=bk.logfile) assert len(stack) >= 2 bop = stack.pop() aop = stack.pop() @@ -1940,7 +1939,7 @@ def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): assert len(bop) == 1 result = do_box_funcs(tIsectFuncs, aop[0], bop[0]) spush(result) - if blah: print >> bk.logfile, "tIsect post", stack + if blah: print("tIsect post", stack, file=bk.logfile) elif opcode == 0x19: # tAttr subop, nc = unpack("> bk.logfile, " subop=%02xh subname=t%s sz=%d nc=%02xh" % (subop, subname, sz, nc) + if blah: print(" subop=%02xh subname=t%s sz=%d nc=%02xh" % (subop, subname, sz, nc), file=bk.logfile) elif opcode == 0x17: # tStr if bv <= 70: - nc = ord(data[pos+1]) + nc = BYTES_ORD(data[pos+1]) strg = data[pos+2:pos+2+nc] # left in 8-bit encoding sz = nc + 2 else: strg, newpos = unpack_unicode_update_pos(data, pos+1, lenlen=1) sz = newpos - pos - if blah: print >> bk.logfile, " sz=%d strg=%r" % (sz, strg) + if blah: print(" sz=%d strg=%r" % (sz, strg), file=bk.logfile) else: if sz <= 0: - print >> bk.logfile, "**** Dud size; exiting ****" + print("**** Dud size; exiting ****", file=bk.logfile) return pos += sz continue @@ -1969,76 +1968,76 @@ def dump_formula(bk, data, fmlalen, bv, reldelta, blah=0, isname=0): elif opcode == 0x01: # tFunc nb = 1 + int(bv >= 40) funcx = unpack("<" + " BH"[nb], data[pos+1:pos+1+nb]) - if blah: print >> bk.logfile, " FuncID=%d" % funcx + if blah: print(" FuncID=%d" % funcx, file=bk.logfile) elif opcode == 0x02: #tFuncVar nb = 1 + int(bv >= 40) nargs, funcx = unpack("> bk.logfile, " FuncID=%d nargs=%d macro=%d prompt=%d" % (funcx, nargs, macro, prompt) + if blah: print(" FuncID=%d nargs=%d macro=%d prompt=%d" % (funcx, nargs, macro, prompt), file=bk.logfile) elif opcode == 0x03: #tName namex = unpack("> bk.logfile, " namex=%d" % namex + if blah: print(" namex=%d" % namex, file=bk.logfile) elif opcode == 0x04: # tRef res = get_cell_addr(data, pos+1, bv, reldelta) - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) elif opcode == 0x05: # tArea res = get_cell_range_addr(data, pos+1, bv, reldelta) - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) elif opcode == 0x09: # tMemFunc nb = unpack("> bk.logfile, " %d bytes of cell ref formula" % nb + if blah: print(" %d bytes of cell ref formula" % nb, file=bk.logfile) elif opcode == 0x0C: #tRefN res = get_cell_addr(data, pos+1, bv, reldelta=1) # note *ALL* tRefN usage has signed offset for relative addresses any_rel = 1 - if blah: print >> bk.logfile, " ", res + if blah: print(" ", res, file=bk.logfile) elif opcode == 0x0D: #tAreaN 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 + if blah: print(" ", res, file=bk.logfile) elif opcode == 0x1A: # tRef3d refx = unpack("> bk.logfile, " ", refx, res + if blah: print(" ", refx, res, file=bk.logfile) rowx, colx, row_rel, col_rel = res any_rel = any_rel or row_rel or col_rel shx1, shx2 = get_externsheet_local_range(bk, refx, blah) any_err |= shx1 < -1 coords = (shx1, shx2+1, rowx, rowx+1, colx, colx+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) if optype == 1: spush([coords]) elif opcode == 0x1B: # tArea3d refx = unpack("> bk.logfile, " ", refx, res1, res2 + if blah: print(" ", refx, res1, res2, file=bk.logfile) rowx1, colx1, row_rel1, col_rel1 = res1 rowx2, colx2, row_rel2, col_rel2 = res2 any_rel = any_rel or row_rel1 or col_rel1 or row_rel2 or col_rel2 shx1, shx2 = get_externsheet_local_range(bk, refx, blah) any_err |= shx1 < -1 coords = (shx1, shx2+1, rowx1, rowx2+1, colx1, colx2+1) - if blah: print >> bk.logfile, " ", coords + if blah: print(" ", coords, file=bk.logfile) if optype == 1: spush([coords]) elif opcode == 0x19: # tNameX refx, namex = unpack("> bk.logfile, " refx=%d namex=%d" % (refx, namex) - elif is_error_opcode(opcode): + if blah: print(" refx=%d namex=%d" % (refx, namex), file=bk.logfile) + elif opcode in error_opcodes: any_err = 1 else: - if blah: print >> bk.logfile, "FORMULA: /// Not handled yet: t" + oname + if blah: print("FORMULA: /// Not handled yet: t" + oname, file=bk.logfile) any_err = 1 if sz <= 0: - print >> bk.logfile, "**** Dud size; exiting ****" + print("**** Dud size; exiting ****", file=bk.logfile) return pos += sz if blah: - print >> bk.logfile, "End of formula. any_rel=%d any_err=%d stack=%r" % \ - (not not any_rel, any_err, stack) + print("End of formula. any_rel=%d any_err=%d stack=%r" % + (not not any_rel, any_err, stack), file=bk.logfile) if len(stack) >= 2: - print >> bk.logfile, "*** Stack has unprocessed args" + print("*** Stack has unprocessed args", file=bk.logfile) # === Some helper functions for displaying cell references === @@ -2076,16 +2075,12 @@ def colnamerel(colx, colxrel, bcolx=None, r1c1=0): return "C" return colname((bcolx + colx) % 256) -## -# Utility function: (5, 7) => 'H6' def cellname(rowx, colx): - """ (5, 7) => 'H6' """ + """Utility function: ``(5, 7)`` => ``'H6'``""" return "%s%d" % (colname(colx), rowx+1) -## -# Utility function: (5, 7) => '$H$6' def cellnameabs(rowx, colx, r1c1=0): - """ (5, 7) => '$H$6' or 'R8C6'""" + """Utility function: ``(5, 7)`` => ``'$H$6'``""" if r1c1: return "R%dC%d" % (rowx+1, colx+1) return "$%s$%d" % (colname(colx), rowx+1) @@ -2102,10 +2097,8 @@ def cellnamerel(rowx, colx, rowxrel, colxrel, browx=None, bcolx=None, r1c1=0): return r + c return c + r -## -# Utility function: 7 => 'H', 27 => 'AB' def colname(colx): - """ 7 => 'H', 27 => 'AB' """ + """Utility function: ``7`` => ``'H'``, ``27`` => ``'AB'``""" alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" if colx <= 25: return alphabet[colx] @@ -2114,39 +2107,47 @@ def colname(colx): return alphabet[xdiv26 - 1] + alphabet[xmod26] def rangename2d(rlo, rhi, clo, chi, r1c1=0): - """ (5, 20, 7, 10) => '$H$6:$J$20' """ + """ ``(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): +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) - ) -## -# Utility function: -#
Ref3D((1, 4, 5, 20, 7, 10)) => 'Sheet2:Sheet3!$H$6:$J$20' + cellnamerel(rhi-1, chi-1, rhirel, chirel, browx, bcolx, r1c1), + ) + + def rangename3d(book, ref3d): - """ Ref3D(1, 4, 5, 20, 7, 10) => 'Sheet2:Sheet3!$H$6:$J$20' - (assuming Excel's default sheetnames) """ + """ + 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])) -## -# Utility function: -#
Ref3D(coords=(0, 1, -32, -22, -13, 13), relflags=(0, 0, 1, 1, 1, 1)) -# R1C1 mode => 'Sheet1!R[-32]C[-13]:R[-23]C[12]' -# A1 mode => depends on base cell (browx, bcolx) 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]) @@ -2164,7 +2165,7 @@ def quotedsheetname(shnames, shx): -2: "internal; deleted sheet", -3: "internal; macro sheet", -4: "<>", - }.get(shx, "?error %d?" % shx) + }.get(shx, "?error %d?" % shx) if "'" in shname: return "'" + shname.replace("'", "''") + "'" if " " in shname: @@ -2178,7 +2179,9 @@ def sheetrange(book, slo, shi): shdesc += ":" + quotedsheetname(shnames, shi-1) return shdesc -def sheetrangerel(book, (slo, shi), (slorel, shirel)): +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 diff --git a/xlrd/info.py b/xlrd/info.py index 830e38b4..eb7de6f4 100644 --- a/xlrd/info.py +++ b/xlrd/info.py @@ -1 +1 @@ -__VERSION__ = "0.8.0" +__version__ = __VERSION__ = "2.0.2" diff --git a/xlrd/licences.py b/xlrd/licences.py deleted file mode 100644 index 1e262a97..00000000 --- a/xlrd/licences.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- coding: cp1252 -*- - -""" -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. -""" - -""" -/*- - * 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. - */ -""" diff --git a/xlrd/sheet.py b/xlrd/sheet.py index 80f6f2eb..376dc4f8 100644 --- a/xlrd/sheet.py +++ b/xlrd/sheet.py @@ -1,38 +1,20 @@ -# -*- coding: cp1252 -*- - -## -#

Portions copyright © 2005-2012 Stephen John Machin, Lingfo Pty Ltd

-#

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

-## - -# 2010-04-25 SJM fix zoom factors cooking logic -# 2010-04-15 CW r4253 fix zoom factors cooking logic -# 2010-04-09 CW r4248 add a flag so xlutils knows whether or not to write a PANE record -# 2010-03-29 SJM Fixed bug in adding new empty rows in put_cell_ragged -# 2010-03-28 SJM Tailored put_cell method for each of ragged_rows=False (fixed speed regression) and =True (faster) -# 2010-03-25 CW r4236 Slight refactoring to remove method calls -# 2010-03-25 CW r4235 Collapse expand_cells into put_cell and enhance the raggedness. This should save even more memory! -# 2010-03-25 CW r4234 remove duplicate chunks for extend_cells; refactor to remove put_number_cell and put_blank_cell which essentially duplicated the code of put_cell -# 2010-03-10 SJM r4222 Added reading of the PANE record. -# 2010-03-10 SJM r4221 Preliminary work on "cooked" mag factors; use at own peril -# 2010-03-01 SJM Reading SCL record -# 2010-03-01 SJM Added ragged_rows functionality -# 2009-08-23 SJM Reduced CPU time taken by parsing MULBLANK records. -# 2009-08-18 SJM Used __slots__ and sharing to reduce memory consumed by Rowinfo instances -# 2009-05-31 SJM Fixed problem with no CODEPAGE record on extremely minimal BIFF2.x 3rd-party file -# 2009-04-27 SJM Integrated on_demand patch by Armando Serrano Lombillo -# 2008-02-09 SJM Excel 2.0: build XFs on the fly from cell attributes -# 2007-12-04 SJM Added support for Excel 2.x (BIFF2) files. -# 2007-10-11 SJM Added missing entry for blank cell type to ctype_text -# 2007-07-11 SJM Allow for BIFF2/3-style FORMAT record in BIFF4/8 file -# 2007-04-22 SJM Remove experimental "trimming" facility. - -from biffh import * -from timemachine import * -from struct import unpack, calcsize -from formula import dump_formula, decompile_formula, rangename2d, FMLA_TYPE_CELL, FMLA_TYPE_SHARED -from formatting import nearest_colour_index, Format -import time +# -*- 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 @@ -61,257 +43,287 @@ # The real thing is the visibility attribute from the BOUNDSHEET record. ("sheet_visible", 0), ("show_in_page_break_preview", 0), - ) +) -## -#

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 {@link #Cell} class.

-# -#

WARNING: You don't call this class yourself. You access Sheet objects via the Book object that -# was returned when you called xlrd.open_workbook("myfile.xls").

class Sheet(BaseObject): - ## - # Name of sheet. + """ + 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 Book object to which this sheet belongs. - # Example usage: some_sheet.book.datemode + #: 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). + + #: Number of rows in sheet. A row index is in ``range(thesheet.nrows)``. nrows = 0 - ## - # Nominal number of columns in sheet. It is 1 + the maximum column index - # found, ignoring trailing empty cells. See also open_workbook(ragged_rows=?) - # and Sheet.{@link #Sheet.row_len}(row_index). + #: 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 {@link #Colinfo} object. Often there is an entry - # in COLINFO records for all column indexes in range(257). - # Note that xlrd ignores the entry for the non-existent - # 257th column. On the other hand, there may be no entry for unused columns. - #
-- New in version 0.6.1. Populated only if open_workbook(formatting_info=True). + + #: 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 {@link #Rowinfo} object. Note that it is possible - # to have missing entries -- at least one source of XLS files doesn't - # bother writing ROW records. - #
-- New in version 0.6.1. Populated only if open_workbook(formatting_info=True). + #: 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. - #
-- New in version 0.6.0 - #
How to deconstruct the list: - #
-    # 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))
-    # 
+ #: 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 col_label_ranges above. - #
-- New in version 0.6.0 + #: 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. - #
-- New in version 0.6.1. Extracted only if open_workbook(formatting_info=True). - #
How to deconstruct the list: - #
-    # 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.
-    # 
+ #: 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: - #
-    # runlist = thesheet.rich_text_runlist_map.get((rowx, colx))
-    # if runlist:
-    #     for offset, font_index in runlist:
-    #         # do work here.
-    #         pass
-    # 
- # Populated only if open_workbook(formatting_info=True). - #
-- New in version 0.7.2. - #
  - 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 {@link #Colinfo} class. - #
-- New in version 0.6.1 + + #: 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 {@link #Colinfo} class. - #
-- New in version 0.6.1 + #: 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 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 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 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 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 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 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 {@link #Colinfo} class for discussion. + #: 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 {@link #Hyperlink} objects corresponding to HLINK records found - # in the worksheet.
-- New in version 0.7.2

+ #: 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 {@link #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 hyperlink_list. - #
-- New in version 0.7.2

+ #: 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 {@link #Note} object. - # Cells not containing a note ("comment") are not mapped. - #
-- New in version 0.7.2

- cell_note_map = {} - - ## - # Number of columns in left pane (frozen panes; for split panes, see comments below in code) + #: 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 below in code) + #: 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 + #: 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 + #: 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. + #: 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 + #: 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). - #
-- New in version 0.7.2 + #: 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). - #
-- New in version 0.7.2 + #: 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.pickleable = book.pickleable - if array_array and (CAN_PICKLE_ARRAY or not book.pickleable): - # use array - self.bt = array_array('B', [XL_CELL_EMPTY]) - self.bf = array_array('h', [-1]) - else: - # don't use array - self.bt = [XL_CELL_EMPTY] - self.bf = [-1] + self.bt = array('B', [XL_CELL_EMPTY]) + self.bf = array('h', [-1]) self.name = name self.number = number self.verbosity = book.verbosity @@ -369,16 +381,13 @@ def __init__(self, book, position, name, number): self.cooked_normal_view_mag_factor = 100 # Values (if any) actually stored on the XLS file - self.cached_page_break_preview_mag_factor = None # from WINDOW2 record - self.cached_normal_view_mag_factor = None # from WINDOW2 record + 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 - #### Don't initialise this here, use class attribute initialisation. - #### self.gcw = (0, ) * 256 #### - if self.biff_version >= 80: self.utter_max_rows = 65536 else: @@ -392,10 +401,10 @@ def __init__(self, book, position, name, number): # self._put_cell_rows_appended = 0 # self._put_cell_cells_appended = 0 - - ## - # {@link #Cell} object in the given row and column. 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: @@ -404,24 +413,27 @@ def cell(self, rowx, colx): self._cell_types[rowx][colx], self._cell_values[rowx][colx], xfx, - ) + ) - ## - # Value of the cell in the given row and column. def cell_value(self, rowx, colx): + "Value of the cell in the given row and column." return self._cell_values[rowx][colx] - ## - # Type of the cell in the given row and column. - # Refer to the documentation of the {@link #Cell} class. 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] - ## - # XF index of the cell in the given row and column. - # This is an index into Book.{@link #Book.xf_list}. - #
-- New in version 0.6.1 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: @@ -446,41 +458,66 @@ def cell_xf_index(self, rowx, colx): self._xf_index_stats[3] += 1 return 15 - ## - # 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 {@link #Sheet.ncols} cells. - #
-- New in version 0.7.2 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]) - ## - # Returns a sequence of the {@link #Cell} objects in the given row. 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 - ## - # Returns a slice of the types - # of the cells in the given row. 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] - ## - # Returns a slice of the values - # of the cells in the given row. 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] - ## - # Returns a slice of the {@link #Cell} objects in the given row. 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 @@ -493,11 +530,12 @@ def row_slice(self, rowx, start_colx=0, end_colx=None): return [ self.cell(rowx, colx) for colx in xrange(start_colx, end_colx) - ] + ] - ## - # Returns a slice of the {@link #Cell} objects in the given column. 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 @@ -510,11 +548,12 @@ def col_slice(self, colx, start_rowx=0, end_rowx=None): return [ self.cell(rowx, colx) for rowx in xrange(start_rowx, end_rowx) - ] + ] - ## - # Returns a slice of the values of the cells in the given column. 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 @@ -527,11 +566,12 @@ def col_values(self, colx, start_rowx=0, end_rowx=None): return [ self._cell_values[rowx][colx] for rowx in xrange(start_rowx, end_rowx) - ] + ] - ## - # Returns a slice of the types of the cells in the given column. 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 @@ -544,13 +584,8 @@ def col_types(self, colx, start_rowx=0, end_rowx=None): return [ self._cell_types[rowx][colx] for rowx in xrange(start_rowx, end_rowx) - ] + ] - ## - # Returns a sequence of the {@link #Cell} objects in the given column. - def col(self, colx): - return self.col_slice(colx) - # Above two lines just for the docs. Here's the real McCoy: col = col_slice # === Following methods are used in building the worksheet. @@ -558,18 +593,18 @@ def col(self, colx): def tidy_dimensions(self): if self.verbosity >= 3: - fprintf(self.logfile, + 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): + 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) @@ -577,14 +612,16 @@ def tidy_dimensions(self): 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, -1) - if self.verbosity >= 1 \ - and (self.nrows != self._dimnrows or self.ncols != self._dimncols): - fprintf(self.logfile, + 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, @@ -592,7 +629,7 @@ def tidy_dimensions(self): self._dimncols, self.nrows, self.ncols, - ) + ) if not self.ragged_rows: # fix ragged rows ncols = self.ncols @@ -610,7 +647,7 @@ def tidy_dimensions(self): rlen = len(trow) nextra = ncols - rlen if nextra > 0: - s_cell_values[rowx][rlen:] = [''] * nextra + 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 @@ -659,19 +696,19 @@ def put_cell_ragged(self, rowx, colx, ctype, value, xf_index): num_empty += 1 # self._put_cell_row_widenings += 1 # types_row.extend(self.bt * num_empty) - # values_row.extend([''] * 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:] = [''] * 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 >> self.logfile, "put_cell", rowx, colx + except Exception: + print("put_cell", rowx, colx, file=self.logfile) raise def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): @@ -702,7 +739,7 @@ def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): 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 optmisation. + # This very rare case ruins this optimisation. self._first_full_rowx = -2 elif rowx > self._first_full_rowx > -2: self._first_full_rowx = rowx @@ -717,7 +754,7 @@ def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): trow.extend(self.bt * nextra) if self.formatting_info: self._cell_xf_indexes[rowx].extend(self.bf * nextra) - self._cell_values[rowx].extend([''] * nextra) + self._cell_values[rowx].extend([UNICODE_LITERAL('')] * nextra) else: scta = self._cell_types.append scva = self._cell_values.append @@ -729,7 +766,7 @@ def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): for _unused in xrange(self.nrows, nr): # self._put_cell_rows_appended += 1 scta(bt * nc) - scva([''] * nc) + scva([UNICODE_LITERAL('')] * nc) if fmt_info: scxa(bf * nc) self.nrows = nr @@ -739,12 +776,12 @@ def put_cell_unragged(self, rowx, colx, ctype, value, xf_index): self._cell_values[rowx][colx] = value if self.formatting_info: self._cell_xf_indexes[rowx][colx] = xf_index - except: - print >> self.logfile, "put_cell", rowx, colx + except Exception: + print("put_cell", rowx, colx, file=self.logfile) raise - except: - print >> self.logfile, "put_cell", rowx, colx - raise + except Exception: + print("put_cell", rowx, colx, file=self.logfile) + raise # === Methods after this line neither know nor care about how cells are stored. @@ -761,7 +798,7 @@ def read(self, bk): 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 @@ -806,7 +843,7 @@ def read(self, bk): rowx, colx, xf_index = local_unpack('> self.logfile, \ - "*** NOTE: ROW record has row index %d; " \ - "should have 0 <= rowx < %d -- record ignored!" \ - % (rowx, self.utter_max_rows) + print("*** NOTE: ROW record has row index %d; " + "should have 0 <= rowx < %d -- record ignored!" + % (rowx, self.utter_max_rows), file=self.logfile) continue key = (bits1, bits2) r = rowinfo_sharing_dict.get(key) @@ -887,7 +923,7 @@ def read(self, bk): "**ROW %d %d %d\n", self.number, rowx, r.xf_index) if blah_rows: - print >> self.logfile, 'ROW', rowx, bits1, bits2 + 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 @@ -895,25 +931,20 @@ def read(self, bk): # 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('> self.logfile, \ - "*** NOTE: COLINFO record has first col index %d, last %d; " \ - "should have 0 <= first <= last <= 255 -- record ignored!" \ - % (first_colx, last_colx) + print("*** NOTE: COLINFO record has first col index %d, last %d; " + "should have 0 <= first <= last <= 255 -- record ignored!" + % (first_colx, last_colx), file=self.logfile) del c continue upkbits(c, flags, ( @@ -997,7 +1027,7 @@ def read(self, bk): # Maybe it's "locked" by analogy with XFProtection data. ( 8, 0x0700, 'outline_level'), (12, 0x1000, 'collapsed'), - )) + )) for colx in xrange(first_colx, last_colx+1): if colx > 255: break # Excel does 0 to 256 inclusive self.colinfo_map[colx] = c @@ -1010,20 +1040,20 @@ def read(self, bk): 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("> self.logfile, 'DEFCOLWIDTH', self.defcolwidth + if 0: print('DEFCOLWIDTH', self.defcolwidth, file=self.logfile) elif rc == XL_STANDARDWIDTH: if data_len != 2: - print >> self.logfile, '*** ERROR *** STANDARDWIDTH', data_len, repr(data) + print('*** ERROR *** STANDARDWIDTH', data_len, repr(data), file=self.logfile) self.standardwidth, = local_unpack("> self.logfile, 'STANDARDWIDTH', self.standardwidth + if 0: print('STANDARDWIDTH', self.standardwidth, file=self.logfile) elif rc == XL_GCW: if not fmt_info: continue # useless w/o COLINFO assert data_len == 34 - assert data[0:2] == "\x20\x00" + assert data[0:2] == b"\x20\x00" iguff = unpack("<8i", data[2:34]) gcw = [] for bits in iguff: @@ -1033,7 +1063,7 @@ def read(self, bk): self.gcw = tuple(gcw) if 0: showgcw = "".join(map(lambda x: "F "[x], gcw)).rstrip().replace(' ', '.') - print >> self.logfile, "GCW:", showgcw + print("GCW:", showgcw, file=self.logfile) elif rc == XL_BLANK: if not fmt_info: continue rowx, colx, xf_index = local_unpack('> self.logfile, "SHEET.READ: EOF" + if DEBUG: print("SHEET.READ: EOF", file=self.logfile) eof_found = 1 break elif rc == XL_OBJ: @@ -1096,36 +1130,35 @@ def read(self, bk): elif rc in bofcodes: ##### EMBEDDED BOF ##### version, boftype = local_unpack('> self.logfile, \ - "*** Unexpected embedded BOF (0x%04x) at offset %d: version=0x%04x type=0x%04x" \ - % (rc, bk._position - data_len - 4, version, boftype) + print("*** Unexpected embedded BOF (0x%04x) at offset %d: version=0x%04x type=0x%04x" + % (rc, bk._position - data_len - 4, version, boftype), file=self.logfile) while 1: code, data_len, data = bk.get_record_parts() if code == XL_EOF: break - if DEBUG: print >> self.logfile, "---> found EOF" + if DEBUG: print("---> 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, - ) + 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, - ) + 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("> self.logfile, "ARRAY:", row1x, rownx, col1x, colnx, array_flags + print("ARRAY:", row1x, rownx, col1x, colnx, array_flags, file=self.logfile) # dump_formula(bk, data[14:], tokslen, bv, reldelta=0, blah=1) elif rc == XL_SHRFMLA: row1x, rownx, col1x, colnx, nfmlas, tokslen = \ local_unpack("> self.logfile, "SHRFMLA (main):", row1x, rownx, col1x, colnx, nfmlas + print("SHRFMLA (main):", row1x, rownx, col1x, colnx, nfmlas, file=self.logfile) decompile_formula(bk, data[10:], tokslen, FMLA_TYPE_SHARED, blah=1, browx=rowx, bcolx=colx, r1c1=r1c1) elif rc == XL_CONDFMT: @@ -1134,25 +1167,27 @@ def read(self, bk): 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" \ + 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" \ + fprintf( + self.logfile, + "*** %d individual range(s):\n" "*** %s\n", len(olist), - ", ".join([rangename2d(*coords) for coords in 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("> 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" \ + 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_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" \ + 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 @@ -1194,17 +1230,13 @@ def read(self, bk): fmla1 = data[pos:pos+sz1] pos += sz1 if blah and sz1: - fprintf(self.logfile, - "*** formula 1:\n", - ) + 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", - ) + fprintf(self.logfile, "*** formula 2:\n") dump_formula(bk, fmla2, sz2, bv, reldelta=0, blah=1) elif rc == XL_DEFAULTROWHEIGHT: if data_len == 4: @@ -1213,12 +1245,12 @@ def read(self, bk): self.default_row_height, = unpack("= 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 + ( + 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, + ( + options, + self.first_visible_rowx, self.first_visible_colx, ) = unpack("= 0: - print >> self.logfile, ( + print( "WARNING *** SCL rcd sheet %d: should have 0.1 <= num/den <= 4; got %d/%d" - % (self.number, num, den) - ) + % (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, + self.vert_split_pos, + self.horz_split_pos, + self.horz_split_first_visible, + self.vert_split_first_visible, + self.split_active_pane, ) = unpack("> self.logfile, \ - "*** NOTE: ROW_B2 record has row index %d; " \ - "should have 0 <= rowx < %d -- record ignored!" \ - % (rowx, self.utter_max_rows) + print("*** NOTE: ROW_B2 record has row index %d; " + "should have 0 <= rowx < %d -- record ignored!" + % (rowx, self.utter_max_rows), file=self.logfile) continue if not (bits2 & 1): # has_default_xf_index is false xf_index = -1 elif data_len == 18: # Seems the XF index in the cell_attr is dodgy - xfx = local_unpack('> self.logfile, 'ROW_B2', rowx, bits1, has_defaults + 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 @@ -1410,13 +1442,12 @@ def read(self, bk): first_colx, last_colx, width\ = local_unpack("> self.logfile, \ - "*** NOTE: COLWIDTH record has first col index %d, last %d; " \ - "should have first <= last -- record ignored!" \ - % (first_colx, last_colx) + print("*** NOTE: COLWIDTH record has first col index %d, last %d; " + "should have first <= last -- record ignored!" + % (first_colx, last_colx), file=self.logfile) continue for colx in xrange(first_colx, last_colx+1): - if self.colinfo_map.has_key(colx): + if colx in self.colinfo_map: c = self.colinfo_map[colx] else: c = Colinfo() @@ -1426,8 +1457,8 @@ def read(self, bk): fprintf( self.logfile, "COLWIDTH sheet #%d cols %d-%d: wid=%d\n", - self.number, first_colx, last_colx, width - ) + self.number, first_colx, last_colx, width, + ) elif rc == XL_COLUMNDEFAULT: # BIFF2 only if not fmt_info: continue first_colx, last_colx = local_unpack("> self.logfile, \ - "*** NOTE: COLUMNDEFAULT record has first col index %d, last %d; " \ - "should have 0 <= first < last <= 256" \ - % (first_colx, last_colx) + print("*** NOTE: COLUMNDEFAULT record has first col index %d, last %d; " + "should have 0 <= first < last <= 256" + % (first_colx, last_colx), file=self.logfile) last_colx = min(last_colx, 256) for colx in xrange(first_colx, last_colx): offset = 4 + 3 * (colx - first_colx) cell_attr = data[offset:offset+3] xf_index = self.fixed_BIFF2_xfindex(cell_attr, rowx=-1, colx=colx) - if self.colinfo_map.has_key(colx): + if colx in self.colinfo_map: c = self.colinfo_map[colx] else: c = Colinfo() @@ -1458,26 +1488,25 @@ def read(self, bk): attr_names = ("show_formulas", "show_grid_lines", "show_sheet_headers", "panes_are_frozen", "show_zero_values") for attr, char in zip(attr_names, data[0:5]): - setattr(self, attr, int(char != "\x00")) - (self.first_visible_rowx, self.first_visible_colx, - self.automatic_grid_line_colour, + setattr(self, attr, int(char != b'\0')) + ( + self.first_visible_rowx, self.first_visible_colx, + self.automatic_grid_line_colour, ) = unpack("= 80: - flag = ord(data[offset]) & 1 + flag = BYTES_ORD(data[offset]) & 1 enc = ("latin_1", "utf_16_le")[flag] offset += 1 chunk = unicode(data[offset:], enc) @@ -1499,7 +1528,7 @@ def string_record_contents(self, data): if nchars_found == nchars_expected: return result if nchars_found > nchars_expected: - msg = ("STRING/CONTINUE: expected %d chars, found %d" + msg = ("STRING/CONTINUE: expected %d chars, found %d" % (nchars_expected, nchars_found)) raise XLRDError(msg) rc, _unused_len, data = bk.get_record_parts() @@ -1526,10 +1555,11 @@ def update_cooked_mag_factors(self): zoom = self.cached_normal_view_mag_factor if not (10 <= zoom <=400): if blah: - print >> self.logfile, ( + print( "WARNING *** WINDOW2 rcd sheet %d: Bad cached_normal_view_mag_factor: %d" - % (self.number, self.cached_normal_view_mag_factor) - ) + % (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: @@ -1539,15 +1569,16 @@ def update_cooked_mag_factors(self): else: self.cooked_normal_view_mag_factor = self.scl_mag_factor zoom = self.cached_page_break_preview_mag_factor - if zoom == 0: + if not zoom: # VALID, defaults to 60 zoom = 60 elif not (10 <= zoom <= 400): if blah: - print >> self.logfile, ( + print( "WARNING *** WINDOW2 rcd sheet %r: Bad cached_page_break_preview_mag_factor: %r" - % (self.number, self.cached_page_break_preview_mag_factor) - ) + % (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 @@ -1559,7 +1590,7 @@ def fixed_BIFF2_xfindex(self, cell_attr, rowx, colx, true_xfx=None): if true_xfx is not None: xfx = true_xfx else: - xfx = ord(cell_attr[0]) & 0x3F + 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.") @@ -1572,7 +1603,7 @@ def fixed_BIFF2_xfindex(self, cell_attr, rowx, colx, true_xfx=None): # 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 = ord(cell_attr[0]) & 0x3F + 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: @@ -1581,7 +1612,7 @@ def fixed_BIFF2_xfindex(self, cell_attr, rowx, colx, true_xfx=None): 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="\x40\x00\x00", style=xfx < 15) + 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 @@ -1595,12 +1626,12 @@ def insert_new_BIFF20_xf(self, cell_attr, style=0): book.xf_list.append(xf) if blah: xf.dump(self.logfile, header="=== Faked XF %d ===" % xfx, footer="======") - if not book.format_map.has_key(xf.format_key): + 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, u"General") + fmt = Format(xf.format_key, FUN, UNICODE_LITERAL("General")) book.format_map[xf.format_key] = fmt book.format_list.append(fmt) cellty_from_fmtty = { @@ -1609,7 +1640,7 @@ def insert_new_BIFF20_xf(self, cell_attr, style=0): 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 @@ -1617,7 +1648,7 @@ def insert_new_BIFF20_xf(self, cell_attr, style=0): return xfx def fake_XF_from_BIFF20_cell_attr(self, cell_attr, style=0): - from formatting import XF, XFAlignment, XFBorder, XFBackground, XFProtection + from .formatting import XF, XFAlignment, XFBorder, XFBackground, XFProtection xf = XF() xf.alignment = XFAlignment() xf.alignment.indent_level = 0 @@ -1636,7 +1667,7 @@ def fake_XF_from_BIFF20_cell_attr(self, cell_attr, style=0): 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: @@ -1655,8 +1686,15 @@ def fake_XF_from_BIFF20_cell_attr(self, cell_attr, style=0): xf.parent_style_index = (0x0FFF, 0)[style] xf.alignment.vert_align = 2 # bottom xf.alignment.rotation = 0 - for attr_stem in \ - "format font alignment border background protection".split(): + attr_stems = [ + 'format', + 'font', + 'alignment', + 'border', + 'background', + 'protection', + ] + for attr_stem in attr_stems: attr = "_" + attr_stem + "_flag" setattr(xf, attr, 1) return xf @@ -1665,18 +1703,22 @@ def req_fmt_info(self): if not self.formatting_info: raise XLRDError("Feature requires open_workbook(..., formatting_info=True)") - ## - # Determine column display width. - #
-- New in version 0.6.1 - #
- # @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). - 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) @@ -1703,13 +1745,13 @@ def computed_column_width(self, colx): def handle_hlink(self, data): # DEBUG = 1 - if DEBUG: print >> self.logfile, "\n=== hyperlink ===" + 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('> self.logfile, "options: %08X" % options + assert guid0 == b"\xD0\xC9\xEA\x79\xF9\xBA\xCE\x11\x8C\x82\x00\xAA\x00\x4B\xA9\x0B" + assert dummy == b"\x02\x00\x00\x00" + if DEBUG: print("options: %08X" % options, file=self.logfile) offset = 32 def get_nul_terminated_unicode(buf, ofs): @@ -1721,47 +1763,50 @@ def get_nul_terminated_unicode(buf, ofs): if options & 0x14: # has a description h.desc, offset = get_nul_terminated_unicode(data, offset) - + if options & 0x80: # has a target h.target, offset = get_nul_terminated_unicode(data, offset) - + if (options & 1) and not (options & 0x100): # HasMoniker and not MonikerSavedAsString # an OLEMoniker structure clsid, = unpack('<16s', data[offset:offset + 16]) - if DEBUG: print >> self.logfile, "clsid=%r" %clsid + if DEBUG: fprintf(self.logfile, "clsid=%r\n", clsid) offset += 16 - if clsid == "\xE0\xC9\xEA\x79\xF9\xBA\xCE\x11\x8C\x82\x00\xAA\x00\x4B\xA9\x0B": + if clsid == b"\xE0\xC9\xEA\x79\xF9\xBA\xCE\x11\x8C\x82\x00\xAA\x00\x4B\xA9\x0B": # E0H C9H EAH 79H F9H BAH CEH 11H 8CH 82H 00H AAH 00H 4BH A9H 0BH # URL Moniker - h.type = u'url' + h.type = UNICODE_LITERAL('url') nbytes = unpack('> self.logfile, "initial url=%r len=%d" % (h.url_or_path, len(h.url_or_path)) - endpos = h.url_or_path.find(u'\x00') - if DEBUG: print >> self.logfile, "endpos=%d" % endpos + if DEBUG: fprintf(self.logfile, "initial url=%r len=%d\n", h.url_or_path, len(h.url_or_path)) + endpos = h.url_or_path.find('\x00') + if DEBUG: print("endpos=%d" % endpos, file=self.logfile) h.url_or_path = h.url_or_path[:endpos] true_nbytes = 2 * (endpos + 1) offset += true_nbytes extra_nbytes = nbytes - true_nbytes extra_data = data[offset:offset + extra_nbytes] offset += extra_nbytes - if DEBUG: print >> self.logfile, "url=%r" % h.url_or_path - if DEBUG: print >> self.logfile, "extra=%r" % extra_data - if DEBUG: print >> self.logfile, "nbytes=%d true_nbytes=%d extra_nbytes=%d" % (nbytes, true_nbytes, extra_nbytes) + if DEBUG: + fprintf( + self.logfile, + "url=%r\nextra=%r\nnbytes=%d true_nbytes=%d extra_nbytes=%d\n", + h.url_or_path, extra_data, nbytes, true_nbytes, extra_nbytes, + ) assert extra_nbytes in (24, 0) - elif clsid == "\x03\x03\x00\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46": + elif clsid == b"\x03\x03\x00\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x46": # file moniker - h.type = u'local file' + h.type = UNICODE_LITERAL('local file') uplevels, nbytes = unpack("> self.logfile, "uplevels=%d shortpath=%r" % (uplevels, shortpath) + shortpath = b"..\\" * uplevels + data[offset:offset + nbytes - 1] #### BYTES, not unicode + if DEBUG: fprintf(self.logfile, "uplevels=%d shortpath=%r\n", uplevels, shortpath) offset += nbytes offset += 24 # OOo: "unknown byte sequence" # above is version 0xDEAD + 20 reserved zero bytes sz = unpack('> self.logfile, "sz=%d" % sz + if DEBUG: print("sz=%d" % sz, file=self.logfile) offset += 4 if sz: xl = unpack('> self.logfile, "*** unknown clsid %r" % clsid + fprintf(self.logfile, "*** unknown clsid %r\n", clsid) elif options & 0x163 == 0x103: # UNC - h.type = u'unc' + h.type = UNICODE_LITERAL('unc') h.url_or_path, offset = get_nul_terminated_unicode(data, offset) elif options & 0x16B == 8: - h.type = u'workbook' + h.type = UNICODE_LITERAL('workbook') else: - h.type = u'unknown' - + h.type = UNICODE_LITERAL('unknown') + if options & 0x8: # has textmark h.textmark, offset = get_nul_terminated_unicode(data, offset) - assert offset == record_size - if DEBUG: h.dump(header="... object dump ...") + if DEBUG: + h.dump(header="... object dump ...") + print("offset=%d record_size=%d" % (offset, record_size)) + + extra_nbytes = record_size - offset + if extra_nbytes > 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:] == '\x00\x00' + assert data[-2:] == b'\x00\x00' h.quicktip = unicode(data[10:-2], 'utf_16_le') def handle_msodrawingetc(self, recid, data_len, data): @@ -1871,9 +1931,9 @@ def handle_obj(self, data): ( 9, 0x0200, 'scrollbar_flag'), # not documented in Excel 97 dev kit (13, 0x2000, 'autofill'), (14, 0x4000, 'autoline'), - )) + )) elif ft == 0x00: - if data[pos:data_len] == BYTES_X00 * (data_len - pos): + if data[pos:data_len] == b'\0' * (data_len - pos): # ignore "optional reserved" data at end of record break msg = "Unexpected data at end of OBJECT record" @@ -1922,14 +1982,14 @@ def handle_note(self, data, txos): expected_bytes -= nb assert expected_bytes == 0 enc = self.book.encoding or self.book.derive_encoding() - o.text = unicode(''.join(pieces), enc) + o.text = unicode(b''.join(pieces), enc) o.rich_text_runlist = [(0, 0)] o.show = 0 o.row_hidden = 0 o.col_hidden = 0 - o.author = u'' + o.author = UNICODE_LITERAL('') o._object_id = None - self.cell_note_map[o.rowx, o.colx] = o + self.cell_note_map[o.rowx, o.colx] = o return # Excel 8.0+ o.rowx, o.colx, option_flags, o._object_id = unpack('<4H', data[:8]) @@ -1949,13 +2009,12 @@ def handle_note(self, data, txos): if txo: o.text = txo.text o.rich_text_runlist = txo.rich_text_runlist - self.cell_note_map[o.rowx, o.colx] = o + self.cell_note_map[o.rowx, o.colx] = o def handle_txo(self, data): if self.biff_version < 80: return o = MSTxo() - data_len = len(data) fmt = '> self.logfile, o.rich_text_runlist + print(o.rich_text_runlist, file=self.logfile) return o def handle_feat11(self, data): @@ -2022,7 +2081,7 @@ def handle_feat11(self, data): assert rt == 0x872 assert fHdr == 0 assert Ref1 == Ref0 - print >> self.logfile, "FEAT11: grbitFrt=%d Ref0=%r cref=%d cbFeatData=%d" % (grbitFrt, Ref0, cref, cbFeatData) + print(self.logfile, "FEAT11: grbitFrt=%d Ref0=%r cref=%d cbFeatData=%d\n", grbitFrt, Ref0, cref, cbFeatData) # lt: Table data source type: # =0 for Excel Worksheet Table =1 for read-write SharePoint linked List # =2 for XML mapper Table =3 for Query Table @@ -2043,106 +2102,122 @@ def handle_feat11(self, data): (lt, idList, crwHeader, crwTotals, idFieldNext, cbFSData, rupBuild, unusedShort, listFlags, lPosStmCache, cbStmCache, cchStmCache, lem, rgbHashParam, cchName) = unpack('> self.logfile, "lt=%d idList=%d crwHeader=%d crwTotals=%d idFieldNext=%d cbFSData=%d\n"\ - "rupBuild=%d unusedShort=%d listFlags=%04X lPosStmCache=%d cbStmCache=%d\n"\ + print("lt=%d idList=%d crwHeader=%d crwTotals=%d idFieldNext=%d cbFSData=%d\n" + "rupBuild=%d unusedShort=%d listFlags=%04X lPosStmCache=%d cbStmCache=%d\n" "cchStmCache=%d lem=%d rgbHashParam=%r cchName=%d" % ( - lt, idList, crwHeader, crwTotals, idFieldNext, cbFSData, - rupBuild, unusedShort,listFlags, lPosStmCache, cbStmCache, - cchStmCache, lem, rgbHashParam, cchName) + lt, idList, crwHeader, crwTotals, idFieldNext, cbFSData, + rupBuild, unusedShort,listFlags, lPosStmCache, cbStmCache, + cchStmCache, lem, rgbHashParam, cchName), file=self.logfile) + + def __repr__(self): + return "Sheet {:>2}:<{}>".format(self.number, self.name) + class MSODrawing(BaseObject): pass + class MSObj(BaseObject): pass + class MSTxo(BaseObject): pass -## -#

Represents a user "comment" or "note". -# Note objects are accessible through Sheet.{@link #Sheet.cell_note_map}. -#
-- New in version 0.7.2 -#

+ class Note(BaseObject): - ## - # Author of note - author = u'' - ## - # True if the containing column is hidden - col_hidden = 0 - ## - # Column index + """ + 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 Sheet.{@link #Sheet.rich_text_runlist_map}, the first offset should always be 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 + + #: True if the containing row is hidden row_hidden = 0 - ## - # Row index + + #: Row index rowx = 0 - ## - # True if note is always shown + + #: True if note is always shown show = 0 - ## - # Text of the note - text = u'' - -## -#

Contains the attributes of a hyperlink. -# Hyperlink objects are accessible through Sheet.{@link #Sheet.hyperlink_list} -# and Sheet.{@link #Sheet.hyperlink_map}. -#
-- New in version 0.7.2 -#

+ + #: Text of the note + text = UNICODE_LITERAL('') + + class Hyperlink(BaseObject): - ## - # Index of first row + """ + 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 + + #: Index of last row lrowx = None - ## - # Index of first column + + #: Index of first column fcolx = None - ## - # Index of last column + + #: Index of last column lcolx = None - ## - # Type of hyperlink. Unicode string, one of 'url', 'unc', - # 'local file', 'workbook', 'unknown' + + #: 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 bytes (3.x) or str (2.x) string, with unknown encoding. + + #: 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. + + #: 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: I have not seen a case of this. - # It seems impossible to create one in the Excel UI. + + #: 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 - ## - # "Textmark": the piece after the "#" in - # "http://docs.python.org/library#struct_module", or the Sheet1!A1:Z99 - # part when type is "workbook". + + #: 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. + + #: The text of the "quick tip" displayed when the cursor + #: hovers over the hyperlink. quicktip = None # === helpers === def unpack_RK(rk_str): - flags = ord(rk_str[0]) + flags = BYTES_ORD(rk_str[0]) if flags & 2: # There's a SIGNED 30-bit integer in there! i, = unpack('Contains the data for one cell.

-# -#

WARNING: You don't call this class yourself. You access Cell objects -# via methods of the {@link #Sheet} object(s) that you found in the {@link #Book} object that -# was returned when you called xlrd.open_workbook("myfile.xls").

-#

Cell objects have three attributes: ctype is an int, value -# (which depends on ctype) and xf_index. -# If "formatting_info" is not enabled when the workbook is opened, xf_index will be None. -# The following table describes the types of cells and how their values -# are represented in Python.

-# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -# -#
Type symbolType numberPython value
XL_CELL_EMPTY0empty string u''
XL_CELL_TEXT1a Unicode string
XL_CELL_NUMBER2float
XL_CELL_DATE3float
XL_CELL_BOOLEAN4int; 1 means TRUE, 0 means FALSE
XL_CELL_ERROR5int representing internal Excel codes; for a text representation, -# refer to the supplied dictionary error_text_from_code
XL_CELL_BLANK6empty string u''. Note: this type will appear only when -# open_workbook(..., formatting_info=True) is used.
-#

+} + class Cell(BaseObject): + """ + Contains the data for one cell. + + .. warning:: + You don't call this class yourself. You access :class:`Cell` objects + via methods of the :class:`Sheet` object(s) that you found in the + :class:`~xlrd.book.Book` object that was returned when you called + :func:`~xlrd.open_workbook` + + Cell objects have three attributes: ``ctype`` is an int, ``value`` + (which depends on ``ctype``) and ``xf_index``. + If ``formatting_info`` is not enabled when the workbook is opened, + ``xf_index`` will be ``None``. + + The following table describes the types of cells and how their values + are represented in Python. + + .. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type symbolType numberPython value
XL_CELL_EMPTY0empty string ''
XL_CELL_TEXT1a Unicode string
XL_CELL_NUMBER2float
XL_CELL_DATE3float
XL_CELL_BOOLEAN4int; 1 means TRUE, 0 means FALSE
XL_CELL_ERROR5int representing internal Excel codes; for a text representation, + refer to the supplied dictionary error_text_from_code
XL_CELL_BLANK6empty string ''. Note: this type will appear only when + open_workbook(..., formatting_info=True) is used.
+ """ __slots__ = ['ctype', 'value', 'xf_index'] @@ -2250,108 +2332,80 @@ def __repr__(self): else: return "%s:%r (XF:%r)" % (ctype_text[self.ctype], self.value, self.xf_index) -## -# There is one and only one instance of an empty cell -- it's a singleton. This is it. -# You may use a test like "acell is empty_cell". -empty_cell = Cell(XL_CELL_EMPTY, '') +empty_cell = Cell(XL_CELL_EMPTY, UNICODE_LITERAL('')) ##### =============== Colinfo and Rowinfo ============================== ##### -## -# 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 [see footnote!]). -# -#
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.""" -#
-# -# Footnote: 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. -#
"""
-# At the moment (2007-01-17) xlrd is going with the GCW version of the story. -# Reference to the source may be useful: see the computed_column_width(colx) method -# of the Sheet class. -#
-- New in version 0.6.1 -#

class Colinfo(BaseObject): - ## - # Width of the column in 1/256 of the width of the zero character, - # using default font (first FONT record in the file). + """ + 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 to be used for formatting empty cells. xf_index = -1 - ## - # 1 = column is hidden + + #: 1 = column is hidden hidden = 0 - ## - # Value of a 1-bit flag whose purpose is unknown - # but is often seen set to 1 + + #: 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 of the column, in ``range(7)``. + #: (0 = no outline) outline_level = 0 - ## - # 1 = column is collapsed + + #: 1 = column is collapsed collapsed = 0 _USE_SLOTS = 1 -## -#

Height and default formatting information that applies to a row in a sheet. -# Derived from ROW records. -#
-- New in version 0.6.1

-# -#

height: Height of the row, in twips. One twip == 1/20 of a point.

-# -#

has_default_height: 0 = Row has custom height; 1 = Row has default height.

-# -#

outline_level: Outline level of the row (0 to 7)

-# -#

outline_group_starts_ends: 1 = Outline group starts or ends here (depending on where the -# outline buttons are located, see WSBOOL record [TODO ??]), -# and is collapsed

-# -#

hidden: 1 = Row is hidden (manually, or by a filter or outline group)

-# -#

height_mismatch: 1 = Row height and default font height do not match

-# -#

has_default_xf_index: 1 = the xf_index attribute is usable; 0 = ignore it

-# -#

xf_index: Index to default XF record for empty cells in this row. -# Don't use this if has_default_xf_index == 0.

-# -#

additional_space_above: 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.

-# -#

additional_space_below: 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.

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__ = ( @@ -2365,18 +2419,46 @@ class Rowinfo(BaseObject): "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): @@ -2391,7 +2473,7 @@ def __getstate__(self): self.xf_index, self.additional_space_above, self.additional_space_below, - ) + ) def __setstate__(self, state): ( @@ -2405,4 +2487,4 @@ def __setstate__(self, state): self.xf_index, self.additional_space_above, self.additional_space_below, - ) = state + ) = state diff --git a/xlrd/timemachine.py b/xlrd/timemachine.py index cfdfbcdf..a519299e 100644 --- a/xlrd/timemachine.py +++ b/xlrd/timemachine.py @@ -1,91 +1,53 @@ -# -*- coding: ascii -*- - ## #

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.1 to 2.7 +# Currently supported: 2.6 to 2.7, 3.2+ # usage: from timemachine import * -from __future__ import nested_scopes -import sys +from __future__ import print_function -python_version = sys.version_info[:2] # e.g. version 2.4 -> (2, 4) +import sys -CAN_PICKLE_ARRAY = python_version >= (2, 5) -CAN_SUBCLASS_BUILTIN = python_version >= (2, 2) +python_version = sys.version_info[:2] # e.g. version 2.6 -> (2, 6) -if python_version >= (3, 0): # Might work on 3.0 but absolutely no support! +if python_version >= (3, 0): + # Python 3 BYTES_LITERAL = lambda x: x.encode('latin1') + UNICODE_LITERAL = lambda x: x BYTES_ORD = lambda byte: byte - BYTES_NULL = bytes(0) # b'' - BYTES_X00 = bytes(1) # b'\x00' - BYTES_X01 = bytes([1]) # b'\x01' from io import BytesIO as BYTES_IO def fprintf(f, fmt, *vargs): fmt = fmt.replace("%r", "%a") - f.write(fmt % vargs) + 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 - BYTES_NULL = '' - BYTES_X00 = '\x00' - BYTES_X01 = '\x01' from cStringIO import StringIO as BYTES_IO def fprintf(f, fmt, *vargs): - f.write(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 - -if python_version >= (2, 6): - def BUFFER(obj, offset=0, size=None): - if size is None: - return memoryview(obj)[offset:] - return memoryview(obj)[offset:offset+size] -else: - BUFFER = buffer - -try: - from array import array as array_array -except ImportError: - # old version of IronPython? - array_array = None - -try: - object -except NameError: - class object: - pass - -try: - True -except NameError: - setattr(sys.modules['__builtin__'], 'True', 1) - -try: - False -except NameError: - setattr(sys.modules['__builtin__'], 'False', 0) - - -def int_floor_div(x, y): - return divmod(x, y)[0] - -def intbool(x): - if x: - return 1 - return 0 - -if python_version < (2, 3): - def sum(sequence, start=0): - tot = start - for item in aseq: - tot += item - return tot + 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 diff --git a/xlrd/xldate.py b/xlrd/xldate.py index e5f75916..d84c6508 100644 --- a/xlrd/xldate.py +++ b/xlrd/xldate.py @@ -1,59 +1,93 @@ -# -*- coding: cp1252 -*- - +# -*- 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: -## -#

Copyright © 2005-2008 Stephen John Machin, Lingfo Pty Ltd

-#

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

-# -#

Provides function(s) for dealing with Microsoft Excel ™ dates.

-## +- JDN 0 corresponds to noon on Monday November 24 in Gregorian year -4713. -# 2008-10-18 SJM Fix bug in xldate_from_date_tuple (affected some years after 2099) +More importantly: -# 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 +- 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 -from timemachine import int_floor_div as ifd +""" +import datetime _JDN_delta = (2415080 - 61, 2416482 - 1) assert _JDN_delta[1] - _JDN_delta[0] == 1462 -class XLDateError(ValueError): pass - -class XLDateNegative(XLDateError): pass -class XLDateAmbiguous(XLDateError): pass -class XLDateTooLarge(XLDateError): pass -class XLDateBadDatemode(XLDateError): pass -class XLDateBadTuple(XLDateError): pass - -_XLDAYS_TOO_LARGE = (2958466, 2958466 - 1462) # This is equivalent to 10000-01-01 - -## -# 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. -#
WARNING: when using this function to -# interpret the contents of a workbook, you should pass in the Book.datemode -# attribute of that workbook. Whether -# the workbook has ever been anywhere near a Macintosh is irrelevant. -# @return Gregorian (year, month, day, hour, minute, nearest_second). -#
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. -# @throws XLDateNegative xldate < 0.00 -# @throws XLDateAmbiguous The 1900 leap-year problem (datemode == 0 and 1.0 <= xldate < 61.0) -# @throws XLDateTooLarge Gregorian year 10000 or later -# @throws XLDateBadDatemode datemode arg is neither 0 nor 1 -# @throws XLDateError Covers the 4 specific errors +# 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: @@ -82,15 +116,48 @@ def xldate_as_tuple(xldate, datemode): raise XLDateAmbiguous(xldate) jdn = xldays + _JDN_delta[datemode] - yreg = (ifd(ifd(jdn * 4 + 274277, 146097) * 3, 4) + jdn + 1363) * 4 + 3 - mp = ifd(yreg % 1461, 4) * 535 + 333 - d = ifd(mp % 16384, 535) + 1 + 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 (ifd(yreg, 1461) - 4715, mp - 9, d, hour, minute, second) + 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: - return (ifd(yreg, 1461) - 4716, mp + 3, d, hour, minute, second) + 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 @@ -102,18 +169,22 @@ def _leap(y): _days_in_month = (None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) -## -# 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. -# @throws XLDateAmbiguous The 1900 leap-year problem (datemode == 0 and 1.0 <= xldate < 61.0) -# @throws XLDateBadDatemode datemode arg is neither 0 nor 1 -# @throws XLDateBadTuple (year, month, day) is too early/late or has invalid component(s) -# @throws XLDateError Covers the specific errors -def xldate_from_date_tuple((year, month, day), datemode): +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) @@ -125,8 +196,8 @@ def xldate_from_date_tuple((year, month, day), datemode): 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))): + 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 @@ -136,8 +207,8 @@ def xldate_from_date_tuple((year, month, day), datemode): Mp = M + 9 else: Mp = M - 3 - jdn = ifd(1461 * Yp, 4) + ifd(979 * Mp + 16, 32) + \ - day - 1364 - ifd(ifd(Yp + 184, 100) * 3, 4) + 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),)) @@ -145,27 +216,33 @@ def xldate_from_date_tuple((year, month, day), datemode): raise XLDateAmbiguous("Before 1900-03-01: %r" % ((year, month, day),)) return float(xldays) -## -# 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 -# @throws XLDateBadTuple Out-of-range hour, minute, or second -def xldate_from_time_tuple((hour, minute, second)): +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),)) -## -# 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. 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_date_tuple(datetime_tuple[:3], datemode) + xldate_from_time_tuple(datetime_tuple[3:]) - ) + ) diff --git a/xlrd/xlsx.py b/xlrd/xlsx.py deleted file mode 100644 index 25fe1f50..00000000 --- a/xlrd/xlsx.py +++ /dev/null @@ -1,781 +0,0 @@ -# -*- coding: ascii -*- - -## -#

Portions copyright (c) 2008-2012 Stephen John Machin, Lingfo Pty Ltd

-#

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

-## - -DEBUG = 0 - -import sys, zipfile, pprint -import re -from timemachine import * -from book import Book, Name -from biffh import error_text_from_code, XLRDError, XL_CELL_BLANK, XL_CELL_TEXT, XL_CELL_BOOLEAN, XL_CELL_ERROR -from formatting import is_date_format_string, Format, XF -from sheet import Sheet - -DLF = sys.stdout # Default Log File - -ET = None -ET_has_iterparse = False - -def ensure_elementtree_imported(verbosity, logfile): - global ET, ET_has_iterparse - if ET is not None: - return - if "IronPython" in sys.version: - import xml.etree.ElementTree as ET - #### 2.7.2.1: fails later with - #### NotImplementedError: iterparse is not supported on IronPython. (CP #31923) - else: - try: import xml.etree.cElementTree as ET - except ImportError: - try: import cElementTree as ET - except ImportError: - try: import lxml.etree as ET - except ImportError: - try: import xml.etree.ElementTree as ET - except ImportError: - try: import elementtree.ElementTree as ET - except ImportError: - raise Exception("Failed to import an ElementTree implementation") - if hasattr(ET, 'iterparse'): - _dummy_stream = BYTES_IO(BYTES_NULL) - try: - ET.iterparse(_dummy_stream) - ET_has_iterparse = True - except NotImplementedError: - pass - if verbosity: - etree_version = repr([ - (item, getattr(ET, item)) - for item in ET.__dict__.keys() - if item.lower().replace('_', '') == 'version' - ]) - print >> logfile, ET.__file__, ET.__name__, etree_version, ET_has_iterparse - -def split_tag(tag): - pos = tag.rfind('}') + 1 - if pos >= 2: - return tag[:pos], tag[pos:] - return '', tag - -def augment_keys(adict, uri): - # uri must already be enclosed in {} - for x in adict.keys(): - adict[uri + x] = adict[x] - -_UPPERCASE_1_REL_INDEX = {} # Used in fast conversion of column names (e.g. "XFD") to indices (16383) -for _x in xrange(26): - _UPPERCASE_1_REL_INDEX["ABCDEFGHIJKLMNOPQRSTUVWXYZ"[_x]] = _x + 1 -for _x in "123456789": - _UPPERCASE_1_REL_INDEX[_x] = 0 -del _x - -def cell_name_to_rowx_colx(cell_name, letter_value=_UPPERCASE_1_REL_INDEX): - # Extract column index from cell name - # A => 0, Z =>25, AA => 26, XFD => 16383 - colx = 0 - charx = -1 - try: - for c in cell_name: - charx += 1 - lv = letter_value[c] - if lv: - colx = colx * 26 + lv - else: # start of row number; can't be '0' - colx = colx - 1 - assert 0 <= colx < X12_MAX_COLS - break - except KeyError: - raise Exception('Unexpected character %r in cell name %r' % (c, cell_name)) - rowx = int(cell_name[charx:]) - 1 - return rowx, colx - -error_code_from_text = {} -for _code, _text in error_text_from_code.items(): - error_code_from_text[_text] = _code - -# === X12 === Excel 2007 .xlsx =============================================== - -U_SSML12 = "{http://schemas.openxmlformats.org/spreadsheetml/2006/main}" -U_ODREL = "{http://schemas.openxmlformats.org/officeDocument/2006/relationships}" -U_PKGREL = "{http://schemas.openxmlformats.org/package/2006/relationships}" -U_CP = "{http://schemas.openxmlformats.org/package/2006/metadata/core-properties}" -U_DC = "{http://purl.org/dc/elements/1.1/}" -U_DCTERMS = "{http://purl.org/dc/terms/}" -XML_SPACE_ATTR = "{http://www.w3.org/XML/1998/namespace}space" -XML_WHITESPACE = u"\t\n \r" -X12_MAX_ROWS = 2 ** 20 -X12_MAX_COLS = 2 ** 14 -V_TAG = U_SSML12 + 'v' # cell child: value -F_TAG = U_SSML12 + 'f' # cell child: formula -IS_TAG = U_SSML12 + 'is' # cell child: inline string - -def unescape(s, - subber=re.compile(r'_x[0-9A-Fa-f]{4,4}_', re.UNICODE).sub, - repl=lambda mobj: unichr(int(mobj.group(0)[2:6], 16)), - ): - if u"_" in s: - return subber(repl, s) - return s - -if python_version < (2, 2): - def strip_xml_ws(s, - ): - n = len(s) - spos = 0 - while spos < n and s[spos] in XML_WHITESPACE: - spos += 1 - epos = n - 1 - while epos >= spos and s[epos] in XML_WHITESPACE: - epos -= 1 - return s[spos:epos+1] - - def cooked_text(self, elem): - t = elem.text - if t is None: - return u'' - if elem.get(XML_SPACE_ATTR) != 'preserve': - t = strip_xml_ws(t) - return unicode(unescape(t)) -else: - - def cooked_text(self, elem): - t = elem.text - if t is None: - return u'' - if elem.get(XML_SPACE_ATTR) != 'preserve': - t = t.strip(XML_WHITESPACE) - return unicode(unescape(t)) - -def get_text_from_si_or_is(self, elem, r_tag=U_SSML12+'r', t_tag=U_SSML12 +'t'): - "Returns unescaped unicode" - accum = [] - for child in elem: - # self.dump_elem(child) - tag = child.tag - if tag == t_tag: - t = cooked_text(self, child) - if t: # note: .text attribute can be None - accum.append(t) - elif tag == r_tag: - for tnode in child: - if tnode.tag == t_tag: - t = cooked_text(self, tnode) - if t: - accum.append(t) - return u''.join(accum) - -def map_attributes(amap, elem, obj): - for xml_attr, obj_attr, cnv_func_or_const in amap: - if not xml_attr: - setattr(obj, obj_attr, cnv_func_or_const) - continue - if not obj_attr: continue #### FIX ME #### - raw_value = elem.get(xml_attr) - cooked_value = cnv_func_or_const(raw_value) - setattr(obj, obj_attr, cooked_value) - -def cnv_ST_Xstring(s): - if s is None: return u"" - return unicode(s) - -def cnv_xsd_unsignedInt(s): - if not s: - return None - value = int(s) - assert value >= 0 - return value - -def cnv_xsd_boolean(s): - if not s: - return 0 - if s in ("1", "true", "on"): - return 1 - if s in ("0", "false", "off"): - return 0 - raise ValueError("unexpected xsd:boolean value: %r" % s) - - -_defined_name_attribute_map = ( - ("name", "name", cnv_ST_Xstring, ), - ("comment", "", cnv_ST_Xstring, ), - ("customMenu", "", cnv_ST_Xstring, ), - ("description", "", cnv_ST_Xstring, ), - ("help", "", cnv_ST_Xstring, ), - ("statusBar", "", cnv_ST_Xstring, ), - ("localSheetId", "scope", cnv_xsd_unsignedInt, ), - ("hidden", "hidden", cnv_xsd_boolean, ), - ("function", "func", cnv_xsd_boolean, ), - ("vbProcedure", "vbasic", cnv_xsd_boolean, ), - ("xlm", "macro", cnv_xsd_boolean, ), - ("functionGroupId", "funcgroup", cnv_xsd_unsignedInt, ), - ("shortcutKey", "", cnv_ST_Xstring, ), - ("publishToServer", "", cnv_xsd_boolean, ), - ("workbookParameter", "", cnv_xsd_boolean, ), - ("", "any_err", 0, ), - ("", "any_external", 0, ), - ("", "any_rel", 0, ), - ("", "basic_formula_len", 0, ), - ("", "binary", 0, ), - ("", "builtin", 0, ), - ("", "complex", 0, ), - ("", "evaluated", 0, ), - ("", "excel_sheet_index", 0, ), - ("", "excel_sheet_num", 0, ), - ("", "option_flags", 0, ), - ("", "result", None, ), - ("", "stack", None, ), - ) - -def make_name_access_maps(bk): - name_and_scope_map = {} # (name.lower(), scope): Name_object - name_map = {} # name.lower() : list of Name_objects (sorted in scope order) - num_names = len(bk.name_obj_list) - for namex in xrange(num_names): - nobj = bk.name_obj_list[namex] - name_lcase = nobj.name.lower() - key = (name_lcase, nobj.scope) - if name_and_scope_map.has_key(key): - msg = 'Duplicate entry %r in name_and_scope_map' % (key, ) - if 0: - raise XLRDError(msg) - else: - if bk.verbosity: - print >> bk.logfile, msg - name_and_scope_map[key] = nobj - if name_map.has_key(name_lcase): - name_map[name_lcase].append((nobj.scope, nobj)) - else: - name_map[name_lcase] = [(nobj.scope, nobj)] - for key in name_map.keys(): - alist = name_map[key] - alist.sort() - name_map[key] = [x[1] for x in alist] - bk.name_and_scope_map = name_and_scope_map - bk.name_map = name_map - -class X12General(object): - - def process_stream(self, stream, heading=None): - if self.verbosity >= 2 and heading is not None: - fprintf(self.logfile, "\n=== %s ===\n", heading) - self.tree = ET.parse(stream) - getmethod = self.tag2meth.get - for elem in self.tree.getiterator(): - if self.verbosity >= 3: - self.dump_elem(elem) - meth = getmethod(elem.tag) - if meth: - meth(self, elem) - self.finish_off() - - def finish_off(self): - pass - - def dump_elem(self, elem): - fprintf(self.logfile, - "===\ntag=%r len=%d attrib=%r text=%r tail=%r\n", - split_tag(elem.tag)[1], len(elem), elem.attrib, elem.text, elem.tail) - - def dumpout(self, fmt, *vargs): - text = (12 * ' ' + fmt + '\n') % vargs - self.logfile.write(text) - -class X12Book(X12General): - - def __init__(self, bk, logfile=DLF, verbosity=False): - self.bk = bk - self.logfile = logfile - self.verbosity = verbosity - self.bk.nsheets = 0 - self.bk.props = {} - self.relid2path = {} - self.relid2reltype = {} - self.sheet_targets = [] # indexed by sheetx - self.sheetIds = [] # indexed by sheetx - - core_props_menu = { - U_CP+"lastModifiedBy": ("last_modified_by", cnv_ST_Xstring), - U_DC+"creator": ("creator", cnv_ST_Xstring), - U_DCTERMS+"modified": ("modified", cnv_ST_Xstring), - U_DCTERMS+"created": ("created", cnv_ST_Xstring), - } - - def process_coreprops(self, stream): - if self.verbosity >= 2: - fprintf(self.logfile, "\n=== coreProps ===\n") - self.tree = ET.parse(stream) - getmenu = self.core_props_menu.get - props = {} - for elem in self.tree.getiterator(): - if self.verbosity >= 3: - self.dump_elem(elem) - menu = getmenu(elem.tag) - if menu: - attr, func = menu - value = func(elem.text) - props[attr] = value - self.bk.user_name = props.get('last_modified_by') or props.get('creator') - self.bk.props = props - if self.verbosity >= 2: - fprintf(self.logfile, "props: %r\n", props) - self.finish_off() - - def process_rels(self, stream): - if self.verbosity >= 2: - fprintf(self.logfile, "\n=== Relationships ===\n") - tree = ET.parse(stream) - r_tag = U_PKGREL + 'Relationship' - for elem in tree.findall(r_tag): - rid = elem.get('Id') - target = elem.get('Target') - reltype = elem.get('Type').split('/')[-1] - if self.verbosity >= 2: - self.dumpout('Id=%r Type=%r Target=%r', rid, reltype, target) - self.relid2reltype[rid] = reltype - # self.relid2path[rid] = 'xl/' + target - if target.startswith('/'): - self.relid2path[rid] = target[1:] # drop the / - else: - self.relid2path[rid] = 'xl/' + target - - def do_defined_name(self, elem): - #### UNDER CONSTRUCTION #### - if 0 and self.verbosity >= 3: - self.dump_elem(elem) - nobj = Name() - bk = self.bk - nobj.bk = bk - nobj.name_index = len(bk.name_obj_list) - bk.name_obj_list.append(nobj) - nobj.name = elem.get('name') - nobj.raw_formula = None # compiled bytecode formula -- not in XLSX - nobj.formula_text = cooked_text(self, elem) - map_attributes(_defined_name_attribute_map, elem, nobj) - if nobj.scope is None: - nobj.scope = -1 # global - if nobj.name.startswith(u"_xlnm."): - nobj.builtin = 1 - if self.verbosity >= 2: - nobj.dump(header='=== Name object ===') - - def do_defined_names(self, elem): - for child in elem: - self.do_defined_name(child) - make_name_access_maps(self.bk) - - def do_sheet(self, elem): - bk = self.bk - sheetx = bk.nsheets - # print elem.attrib - rid = elem.get(U_ODREL + 'id') - sheetId = int(elem.get('sheetId')) - name = unescape(unicode(elem.get('name'))) - reltype = self.relid2reltype[rid] - target = self.relid2path[rid] - if self.verbosity >= 2: - self.dumpout( - 'sheetx=%d sheetId=%r rid=%r type=%r name=%r', - sheetx, sheetId, rid, reltype, name) - if reltype != 'worksheet': - if self.verbosity >= 2: - self.dumpout('Ignoring sheet of type %r (name=%r)', reltype, name) - return - bk._sheet_visibility.append(True) - sheet = Sheet(bk, position=None, name=name, number=sheetx) - sheet.utter_max_rows = X12_MAX_ROWS - sheet.utter_max_cols = X12_MAX_COLS - bk._sheet_list.append(sheet) - bk._sheet_names.append(name) - bk.nsheets += 1 - self.sheet_targets.append(target) - self.sheetIds.append(sheetId) - - - def do_workbookpr(self, elem): - datemode = cnv_xsd_boolean(elem.get('date1904')) - if self.verbosity >= 2: - self.dumpout('datemode=%r', datemode) - self.bk.datemode = datemode - - tag2meth = { - 'definedNames': do_defined_names, - 'workbookPr': do_workbookpr, - 'sheet': do_sheet, - } - augment_keys(tag2meth, U_SSML12) - -class X12SST(X12General): - - def __init__(self, bk, logfile=DLF, verbosity=0): - self.bk = bk - self.logfile = logfile - self.verbosity = verbosity - if ET_has_iterparse: - self.process_stream = self.process_stream_iterparse - else: - self.process_stream = self.process_stream_findall - - def process_stream_iterparse(self, stream, heading=None): - if self.verbosity >= 2 and heading is not None: - fprintf(self.logfile, "\n=== %s ===\n", heading) - si_tag = U_SSML12 + 'si' - elemno = -1 - sst = self.bk._sharedstrings - for event, elem in ET.iterparse(stream): - if elem.tag != si_tag: continue - elemno = elemno + 1 - if self.verbosity >= 3: - fprintf(self.logfile, "element #%d\n", elemno) - self.dump_elem(elem) - result = get_text_from_si_or_is(self, elem) - sst.append(result) - elem.clear() # destroy all child elements - if self.verbosity >= 2: - self.dumpout('Entries in SST: %d', len(sst)) - if self.verbosity >= 3: - for x, s in enumerate(sst): - print "SST x=%d s=%r" % (x, s) - - def process_stream_findall(self, stream, heading=None): - if self.verbosity >= 2 and heading is not None: - fprintf(self.logfile, "\n=== %s ===\n", heading) - self.tree = ET.parse(stream) - si_tag = U_SSML12 + 'si' - elemno = -1 - sst = self.bk._sharedstrings - for elem in self.tree.findall(si_tag): - elemno = elemno + 1 - if self.verbosity >= 3: - fprintf(self.logfile, "element #%d\n", elemno) - self.dump_elem(elem) - result = get_text_from_si_or_is(self, elem) - sst.append(result) - if self.verbosity >= 2: - self.dumpout('Entries in SST: %d', len(sst)) - -class X12Styles(X12General): - - def __init__(self, bk, logfile=DLF, verbosity=0): - self.bk = bk - self.logfile = logfile - self.verbosity = verbosity - self.xf_counts = [0, 0] - self.xf_type = None - self.fmt_is_date = {} - for x in range(14, 23) + range(45, 48): #### hard-coding FIX ME #### - self.fmt_is_date[x] = 1 - # dummy entry for XF 0 in case no Styles section - self.bk._xf_index_to_xl_type_map[0] = 2 - # fill_in_standard_formats(bk) #### pre-integration kludge - - def do_cellstylexfs(self, elem): - self.xf_type = 0 - - def do_cellxfs(self, elem): - self.xf_type = 1 - - def do_numfmt(self, elem): - formatCode = unicode(elem.get('formatCode')) - numFmtId = int(elem.get('numFmtId')) - is_date = is_date_format_string(self.bk, formatCode) - self.fmt_is_date[numFmtId] = is_date - fmt_obj = Format(numFmtId, is_date + 2, formatCode) - self.bk.format_map[numFmtId] = fmt_obj - if self.verbosity >= 3: - self.dumpout('numFmtId=%d formatCode=%r is_date=%d', numFmtId, formatCode, is_date) - - def do_xf(self, elem): - if self.xf_type != 1: - #### ignoring style XFs for the moment - return - xfx = self.xf_counts[self.xf_type] - self.xf_counts[self.xf_type] = xfx + 1 - xf = XF() - self.bk.xf_list.append(xf) - self.bk.xfcount += 1 - numFmtId = int(elem.get('numFmtId', '0')) - xf.format_key = numFmtId - is_date = self.fmt_is_date.get(numFmtId, 0) - self.bk._xf_index_to_xl_type_map[xfx] = is_date + 2 - if self.verbosity >= 3: - self.dumpout( - 'xfx=%d numFmtId=%d', - xfx, numFmtId, - ) - self.dumpout(repr(self.bk._xf_index_to_xl_type_map)) - - tag2meth = { - 'cellStyleXfs': do_cellstylexfs, - 'cellXfs': do_cellxfs, - 'numFmt': do_numfmt, - 'xf': do_xf, - } - augment_keys(tag2meth, U_SSML12) - -class X12Sheet(X12General): - - def __init__(self, sheet, logfile=DLF, verbosity=0): - self.sheet = sheet - self.logfile = logfile - self.verbosity = verbosity - self.rowx = -1 # We may need to count them. - self.bk = sheet.book - self.sst = self.bk._sharedstrings - self.warned_no_cell_name = 0 - self.warned_no_row_num = 0 - if ET_has_iterparse: - self.process_stream = self.own_process_stream - - def own_process_stream(self, stream, heading=None): - if self.verbosity >= 2 and heading is not None: - fprintf(self.logfile, "\n=== %s ===\n", heading) - getmethod = self.tag2meth.get - row_tag = U_SSML12 + "row" - self_do_row = self.do_row - for event, elem in ET.iterparse(stream): - if elem.tag == row_tag: - self_do_row(elem) - elem.clear() # destroy all child elements (cells) - elif elem.tag == U_SSML12 + "dimension": - self.do_dimension(elem) - self.finish_off() - - def do_dimension(self, elem): - ref = elem.get('ref') # example: "A1:Z99" or just "A1" - if ref: - # print >> self.logfile, "dimension: ref=%r" % ref - last_cell_ref = ref.split(':')[-1] # example: "Z99" - rowx, colx = cell_name_to_rowx_colx(last_cell_ref) - self.sheet._dimnrows = rowx + 1 - self.sheet._dimncols = colx + 1 - - def do_row(self, row_elem): - - def bad_child_tag(child_tag): - raise Exception('cell type %s has unexpected child <%s> at rowx=%r colx=%r' % (cell_type, child_tag, rowx, colx)) - - row_number = row_elem.get('r') - if row_number is None: # Yes, it's optional. - self.rowx += 1 - explicit_row_number = 0 - if self.verbosity and not self.warned_no_row_num: - self.dumpout("no row number; assuming rowx=%d", self.rowx) - self.warned_no_row_num = 1 - else: - self.rowx = int(row_number) - 1 - explicit_row_number = 1 - assert 0 <= self.rowx < X12_MAX_ROWS - rowx = self.rowx - colx = -1 - if self.verbosity >= 3: - self.dumpout(" row_number=%r rowx=%d explicit=%d", - row_number, self.rowx, explicit_row_number) - letter_value = _UPPERCASE_1_REL_INDEX - for cell_elem in row_elem: - cell_name = cell_elem.get('r') - if cell_name is None: # Yes, it's optional. - colx += 1 - if self.verbosity and not self.warned_no_cell_name: - self.dumpout("no cellname; assuming rowx=%d colx=%d", rowx, colx) - self.warned_no_cell_name = 1 - else: - # Extract column index from cell name - # A => 0, Z =>25, AA => 26, XFD => 16383 - colx = 0 - charx = -1 - try: - for c in cell_name: - charx += 1 - lv = letter_value[c] - if lv: - colx = colx * 26 + lv - else: # start of row number; can't be '0' - colx = colx - 1 - assert 0 <= colx < X12_MAX_COLS - break - except KeyError: - raise Exception('Unexpected character %r in cell name %r' % (c, cell_name)) - if explicit_row_number and cell_name[charx:] != row_number: - raise Exception('cell name %r but row number is %r' % (cell_name, row_number)) - xf_index = int(cell_elem.get('s', '0')) - cell_type = cell_elem.get('t', 'n') - tvalue = None - formula = None - if cell_type == 'n': - # n = number. Most frequent type. - # child contains plain text which can go straight into float() - # OR there's no text in which case it's a BLANK cell - for child in cell_elem: - child_tag = child.tag - if child_tag == V_TAG: - tvalue = child.text - elif child_tag == F_TAG: - formula = cooked_text(self, child) - else: - raise Exception('unexpected tag %r' % child_tag) - if not tvalue: - if self.bk.formatting_info: - self.sheet.put_cell(rowx, colx, XL_CELL_BLANK, '', xf_index) - else: - self.sheet.put_cell(rowx, colx, None, float(tvalue), xf_index) - elif cell_type == "s": - # s = index into shared string table. 2nd most frequent type - # child contains plain text which can go straight into int() - for child in cell_elem: - child_tag = child.tag - if child_tag == V_TAG: - tvalue = child.text - elif child_tag == F_TAG: - # formula not expected here, but gnumeric does it. - formula = child.text - else: - bad_child_tag(child_tag) - if not tvalue: - # - if self.bk.formatting_info: - self.sheet.put_cell(rowx, colx, XL_CELL_BLANK, '', xf_index) - else: - value = self.sst[int(tvalue)] - self.sheet.put_cell(rowx, colx, XL_CELL_TEXT, value, xf_index) - elif cell_type == "str": - # str = string result from formula. - # Should have (formula) child; however in one file, all text cells are str with no formula. - # child can contain escapes - for child in cell_elem: - child_tag = child.tag - if child_tag == V_TAG: - tvalue = cooked_text(self, child) - elif child_tag == F_TAG: - formula = cooked_text(self, child) - else: - bad_child_tag(child_tag) - # assert tvalue is not None and formula is not None - # Yuk. Fails with file created by gnumeric -- no tvalue! - self.sheet.put_cell(rowx, colx, XL_CELL_TEXT, tvalue, xf_index) - elif cell_type == "b": - # b = boolean - # child contains "0" or "1" - # Maybe the data should be converted with cnv_xsd_boolean; - # ECMA standard is silent; Excel 2007 writes 0 or 1 - for child in cell_elem: - child_tag = child.tag - if child_tag == V_TAG: - tvalue = child.text - elif child_tag == F_TAG: - formula = cooked_text(self, child) - else: - bad_child_tag(child_tag) - self.sheet.put_cell(rowx, colx, XL_CELL_BOOLEAN, int(tvalue), xf_index) - elif cell_type == "e": - # e = error - # child contains e.g. "#REF!" - for child in cell_elem: - child_tag = child.tag - if child_tag == V_TAG: - tvalue = child.text - elif child_tag == F_TAG: - formula = cooked_text(self, child) - else: - bad_child_tag(child_tag) - value = error_code_from_text[tvalue] - self.sheet.put_cell(rowx, colx, XL_CELL_ERROR, value, xf_index) - elif cell_type == "inlineStr": - # Not expected in files produced by Excel. - # Only possible child is . - # It's a way of allowing 3rd party s/w to write text (including rich text) cells - # without having to build a shared string table - for child in cell_elem: - child_tag = child.tag - if child_tag == IS_TAG: - tvalue = get_text_from_si_or_is(self, child) - else: - bad_child_tag(child_tag) - assert tvalue is not None - self.sheet.put_cell(rowx, colx, XL_CELL_TEXT, tvalue, xf_index) - else: - raise Exception("Unknown cell type %r in rowx=%d colx=%d" % (cell_type, rowx, colx)) - - tag2meth = { - 'row': do_row, - } - augment_keys(tag2meth, U_SSML12) - -def getzflo(zipfile, member_path): - # GET a Zipfile File-Like Object for passing to - # an XML parser - try: - return zipfile.open(member_path) # CPython 2.6 onwards - except AttributeError: - # old way - return BYTES_IO(zipfile.read(member_path)) - -def open_workbook_2007_xml( - zf, - component_names, - logfile=sys.stdout, - verbosity=0, - pickleable=1, - use_mmap=0, - formatting_info=0, - on_demand=0, - ragged_rows=0, - ): - ensure_elementtree_imported(verbosity, logfile) - bk = Book() - bk.logfile = logfile - bk.verbosity = verbosity - bk.pickleable = pickleable - bk.formatting_info = formatting_info - if formatting_info: - raise NotImplementedError("formatting_info=True not yet implemented") - bk.use_mmap = False #### Not supported initially - bk.on_demand = on_demand - if on_demand: - if verbosity: - print >> bk.logfile, "WARNING *** on_demand=True not yet implemented; falling back to False" - bk.on_demand = False - bk.ragged_rows = ragged_rows - - x12book = X12Book(bk, logfile, verbosity) - zflo = getzflo(zf, 'xl/_rels/workbook.xml.rels') - x12book.process_rels(zflo) - del zflo - zflo = getzflo(zf, 'xl/workbook.xml') - x12book.process_stream(zflo, 'Workbook') - del zflo - props_name = 'docProps/core.xml' - if props_name in component_names: - zflo = getzflo(zf, props_name) - x12book.process_coreprops(zflo) - - x12sty = X12Styles(bk, logfile, verbosity) - if 'xl/styles.xml' in component_names: - zflo = getzflo(zf, 'xl/styles.xml') - x12sty.process_stream(zflo, 'styles') - del zflo - else: - # seen in MS sample file MergedCells.xlsx - pass - - sst_fname = 'xl/sharedStrings.xml' - x12sst = X12SST(bk, logfile, verbosity) - if sst_fname in component_names: - zflo = getzflo(zf, sst_fname) - x12sst.process_stream(zflo, 'SST') - del zflo - - for sheetx in range(bk.nsheets): - fname = x12book.sheet_targets[sheetx] - zflo = getzflo(zf, fname) - sheet = bk._sheet_list[sheetx] - x12sheet = X12Sheet(sheet, logfile, verbosity) - heading = "Sheet %r (sheetx=%d) from %r" % (sheet.name, sheetx, fname) - x12sheet.process_stream(zflo, heading) - del zflo - sheet.tidy_dimensions() - - return bk